npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

iif-cb

v1.1.0

Published

Avoid confusing your code due to Promises and callbacks mix

Downloads

3

Readme

iif-cb

At the 0th sight, this is how to install:

npm i iif-cb

And this is how to include it in your NodeJS code:

const iif = require('iif-cb');

Why?

What about a dirty example?

Say you want your users to be able to modify their profiles. It may or may not include changing their email addresses. If your user wants to change their email address, you obviously have to check if the email address belongs to someone else on the database and, if it does, prevent the user from doing any change.

If we were using a simple non-callback-based language such as PHP, the logic behind it would be pretty easy:

if ($_POST['email'] != '') {
    if (check_if_email_exists_on_the_database($_POST['email])) {
        echo "You can't do it";
        exit;
    } else {
        change_user_email($user_id, $_POST['email']);
    }
}

// continue updating other profile details ONLY IF everything is OK about the email
update_name($user_id, $_POST['name']);
update_something_else($user_id, $_POST['something_else]);

However if we try to convert the above code to NodeJS with all its callbacks and promises, we would fall in trouble. Let's try.

if (req.body.email != '') {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
        }
    })
}

// continue updating other profile details ONLY IF everything is OK about the email
// oh wait
// since the above runs inside a callback, this code would run anyway...
update_name(user_id, req.body.name)
update_something_else(user_id, req.body.something_else)

We could try:

if (req.body.email != '') {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
            // continue updating
            update_name(user_id, req.body.name)
            update_something_else(user_id, req.body.something_else)
        }
    })
}

However there is a problem: if the user does not want to update the email address and leave it blank, then "name" and "something_else" would not get updated as well.

Also, if we move the other updating calls above:

update_name(user_id, req.body.name)
update_something_else(user_id, req.body.something_else)

if (req.body.email != '') {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
        }
    })
}

It would not work as well, since we aren't able to know, before, if the email change faced problems and our briefing says:

check if the email address belongs to someone else on the database and, if it does, prevent the user from doing any change

So we could just put an else and duplicate the code, why not?

if (req.body.email != '') {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
            // continue updating
            update_name(user_id, req.body.name)
            update_something_else(user_id, req.body.something_else)
        }
    })
} else {
    // continue updating
    update_name(user_id, req.body.name)
    update_something_else(user_id, req.body.something_else)
}

Yeah that would do the trick. However duplicating code is far of being a good idea. Also if we have lots of inputs to update, this would be a total mess.

What if we put the process continuation inside a function and call it?

var continue_updating = function() {
    // continue updating
    update_name(user_id, req.body.name)
    update_something_else(user_id, req.body.something_else)
}

if (req.body.email != '') {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
            continue_updating();
        }
    })
} else {
    continue_updating();
}

Not that beautiful, but it works.

However, look at that: the code for continue_updating() is written before the email check, however it runs after the email check. It works well, but it's really confusing.

Ok, we could create a true function instead of a callable object. This way:

if (req.body.email != '') {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
            continue_updating();
        }
    })
} else {
    continue_updating();
}

function continue_updating() {
    // continue updating
    update_name(user_id, req.body.name)
    update_something_else(user_id, req.body.something_else)
}

Since Javascript parses each function declaration before actually running the code, that would work as well.

But... what are we doing with our scope? Will we need to pollute our scope every time we face this problem, creating functions and functions that are only going to be rellevant on a specified portion of our code?

The Solution

With iif-cb, this could be easier and more friendly and readable, using anonymous functions only:

const iif = require('iif-cb');

iif ([ condition ], [ callback_if_true(cb) ], [ callback_on_end ])

[condition] can be any function call, variable or expression that can be converted to a boolean value. If it's a function call, it must return a value immediately (no callbacks inside it).

If [condition] is true, then the library calls callback_if_true, which receives one argument (usually named cb) that points to callback_on_end (you have to call it if you want to continue).

If [condition] is false, then the library calls callback_on_end directly.

Some examples:

const iif = require('iif-cb');

iif (1==1, function(cb) {
    console.log("Hello")
    cb() // you have to call this if you want to continue
}, function() {
    console.log("World")
}); // returns "Hello World"

iif (1==1, function(cb) {
    console.log("Hello")
    // we will not call cb() on this example
}, function() {
    console.log("World")
}) // returns "Hello"

iif (1==0, function(cb) { // false condition
    console.log("Hello") // this is not going
    cb();               //  to be called
}, function() {
    console.log("World")
}) // returns "World"

Our example could be written as:

iif (req.body.email != '', function(cb) {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
        } else {
            changeUserEmail(user_id, req.body.email)
            cb(); // cb is the "callback_on_end" function
        }
    })
}, function() {
    // continue updating
    update_name(user_id, req.body.name)
    update_something_else(user_id, req.body.something_else)
})

What about promises?

They are supported as well.

const iifp = require('iif-cb/p');

The format is:

iifp(condition).then( [callback_if_true], [callback_if_false] ).then( [callback_on_end] );

Examples:

iifp(1 == 1).then(function() {
        console.log("Hello")
    }, function() {
        // this will not be run
        console.log("The expression is false");
    }).then(function() {
        console.log("World")
    }) // returns "Hello\nWorld"

iifp(1 == 0).then(function() {
        console.log("Hello"); // this will not run
    }, () => {}).then(function() { // no "else" (reject) function
        console.log("World");
    }) // returns "World"

iifp(1 == 1).then(function() {
        console.log("Hello");
        return new Promise((resolve, reject) => setTimeout(resolve, 1000)) // calling after 1 sec
    }, function() {
        // this will not be run
        console.log("The expression is false");
    }).then(function() {
        console.log("World");
    }) // returns "Hello[1 sec delay]\nWorld"

iifp(1 == 0).then(function() { // false condition
        console.log("Hello") // this is not going to be called
    }, function() {
        console.log("World")
    }) // returns "World"


iifp(1 == 1).then(function() {
        console.log("Hello");
        if (1 == 0) {
            console.log("Will not run")
        } else {
            return Promise.reject();
        }
    }).then(function() {
        console.log("Will not run either")
    }).catch(function() {
        console.log("World")
    }) // returns "Hello\nWorld"

iifp(1 == 0).then(function() { // false condition
        console.log("Hello"); // will not run
    }).then(function() {
        console.log("Will not run either")
    }).catch(function() {
        console.log("World")
    }) // returns "World"

Our dirty example would be so:

iif (req.body.email != '').then(function(cb) {
    checkIfEmailExistsOnTheDatabase(req.body.email, function(result) {
        if(result) {
            res.send("You can't do it")
            return Promise.reject();
        }
    })
}, () => {}).then(function() {
    // continue updating, email check ok or user does not want to change email
    update_name(user_id, req.body.name)
    update_something_else(user_id, req.body.something_else)
}).catch(function() {
    // do nothing, email check failed
})