美文网首页
Promise for dummies, for mummies

Promise for dummies, for mummies

作者: frankgo | 来源:发表于2019-01-23 10:59 被阅读2次

    Javascript Promises are not difficult. However, lots of people find it a little bit hard to understand at the beginning. Therefore, I would like to write down the way I understand promises, in a dummy way.

    Table of Contents

    Understanding Promises

    Creating a Promise

    Consuming Promises

    Chaining Promises

    Promises are Asynchronous

    Promises in ES5, ES6/2015, ES7/Next

    Why Promises and When to Use Them?

    New Kid On the Block: Observables

    Summary

    Understanding Promises

    A Promise in short:

    "Imagine you are a kid. Your mom promises you that she'll get you a new phone next week."

    You don't know if you will get that phone until next week. Your mom can either really buy you a brand new phone, or stand you up and withhold the phone if she is not happy :(.

    That is a promise. A promise has 3 states. They are:

    Pending: You don't know if you will get that phone

    Fulfilled: Mom is happy, she buys you a brand new phone

    Rejected: Your mom is happy, she withholds the phone

    Creating a Promise

    Let's convert this to JavaScript.

    /_ ES5 _/

    var isMomHappy = false;

    // Promise

    var willIGetNewPhone = new Promise(

        function (resolve, reject) {

            if (isMomHappy) {

                var phone = {

                    brand: 'Samsung',

                    color: 'black'

                };

                resolve(phone); // fulfilled

            } else {

                var reason = new Error('mom is not happy');

                reject(reason); // reject

            }

        }

    );

    The code is quite expressive in itself.

    // promise syntax look like this

    new Promise(/_ executor_/ function (resolve, reject) { ... } );

    Consuming Promises

    Now that we have the promise, let's consume it.

    /_ ES5 _/

    ...

    // call our promise

    var askMom = function () {

        willIGetNewPhone

            .then(function (fulfilled) {

                // yay, you got a new phone

                console.log(fulfilled);

            // output: { brand: 'Samsung', color: 'black' }

            })

            .catch(function (error) {

                // oops, mom don't buy it

                console.log(error.message);

            // output: 'mom is not happy'

            });

    };

    askMom();

    Let's run the example and see the result!

    Demo: https://jsbin.com/nifocu/1/edit?js,console

    Result

    Chaining Promises

    Promises are chainable.

    Let's say, you, the kid, promises your friend that you will show them the new phone when your mom buy you one.

    That is another promise. Let's write it!

    ...

    // 2nd promise

    var showOff = function (phone) {

        return new Promise(

            function (resolve, reject) {

                var message = 'Hey friend, I have a new ' +

                    phone.color + ' ' + phone.brand + ' phone';

                resolve(message);

            }

        );

    };

    Notes:

    Free Node eBook

    Build your first Node apps and learn server-side JavaScript.

    Message

    Email

    Name

    Email

    // shorten it

    ...

    // 2nd promise

    var showOff = function (phone) {

        var message = 'Hey friend, I have a new ' +

                    phone.color + ' ' + phone.brand + ' phone';

        return Promise.resolve(message);

    };

    Let's chain the promises. You, the kid can only start the showOff promise after the willIGetNewPhone promise.

    ...

    // call our promise

    var askMom = function () {

        willIGetNewPhone

        .then(showOff) // chain it here

        .then(function (fulfilled) {

                console.log(fulfilled);

            // output: 'Hey friend, I have a new black Samsung phone.'

            })

            .catch(function (error) {

                // oops, mom don't buy it

                console.log(error.message);

            // output: 'mom is not happy'

            });

    };

    That's how easy to chain the promise.

    Promises are Asynchronous

    Promises are asynchronous. Let's log a message before and after we call the promise.

    // call our promise

    var askMom = function () {

        console.log('before asking Mom'); // log before

        willIGetNewPhone

            .then(showOff)

            .then(function (fulfilled) {

                console.log(fulfilled);

            })

            .catch(function (error) {

                console.log(error.message);

            });

        console.log('after asking mom'); // log after

    }

    What is the sequence of expected output? Probably you expect:

    1. before asking Mom

    2. Hey friend, I have a new black Samsung phone.

    3. after asking mom

    However, the actual output sequence is:

    1. before asking Mom

    2. after asking mom

    3. Hey friend, I have a new black Samsung phone.

    Output

    Why? Because life (or JS) waits for no man.

    You, the kid, wouldn't stop playing while waiting for your mom promise (the new phone). Don't you? That's something we call asynchronous, the code will run without blocking or waiting for the result. Anything that need to wait for promise to proceed, you put that in .then.

    Promises in ES5, ES6/2015, ES7/Next

    ES5 - Majority browsers

    The demo code is workable in ES5 environments (all major browsers + NodeJs) if you include Bluebird promise library. It's because ES5 doesn't support promises out of the box. Another famous promise library is Q by Kris Kowal.

    ES6 / ES2015 - Modern browsers, NodeJs v6

    The demo code works out of the box because ES6 supports promises natively. In addition, with ES6 functions, we can further simplify the code with fat arrow => and use const and let.

    Here is an example of ES6 code:

    /_ ES6 _/

    const isMomHappy = true;

    // Promise

    const willIGetNewPhone = new Promise(

        (resolve, reject) => { // fat arrow

            if (isMomHappy) {

                const phone = {

                    brand: 'Samsung',

                    color: 'black'

                };

                resolve(phone);

            } else {

                const reason = new Error('mom is not happy');

                reject(reason);

            }

        }

    );

    const showOff = function (phone) {

        const message = 'Hey friend, I have a new ' +

                    phone.color + ' ' + phone.brand + ' phone';

        return Promise.resolve(message);

    };

    // call our promise

    const askMom = function () {

        willIGetNewPhone

            .then(showOff)

            .then(fulfilled => console.log(fulfilled)) // fat arrow

            .catch(error => console.log(error.message)); // fat arrow

    };

    askMom();

    Notes that all the var are replaced with const. All the function(resolve, reject) has been simplified to (resolve, reject) =>. There are a few benefits come with these changes. Read more on:-

    ES7 - Async Await make the syntax look prettier

    ES7 introduce async and await syntax. It makes the asynchronous syntax look prettier and easier to understand, without the .then and .catch.

    Rewrite our example with ES7 syntax.

    /_ ES7 _/

    const isMomHappy = true;

    // Promise

    const willIGetNewPhone = new Promise(

        (resolve, reject) => {

            if (isMomHappy) {

                const phone = {

                    brand: 'Samsung',

                    color: 'black'

                };

                resolve(phone);

            } else {

                const reason = new Error('mom is not happy');

                reject(reason);

            }

        }

    );

    // 2nd promise

    async function showOff(phone) {

        return new Promise(

            (resolve, reject) => {

                var message = 'Hey friend, I have a new ' +

                    phone.color + ' ' + phone.brand + ' phone';

                resolve(message);

            }

        );

    };

    // call our promise

    async function askMom() {

        try {

            console.log('before asking Mom');

            let phone = await willIGetNewPhone;

            let message = await showOff(phone);

            console.log(message);

            console.log('after asking mom');

        }

        catch (error) {

            console.log(error.message);

        }

    }

    (async () => {

        await askMom();

    })();

    Why Promises and When to Use Them?

    Why do we need promises? How's the world look like before promise? Before answering these questions, let's go back to the fundamental.

    Normal Function vs Async Function

    Let's take a look at these two example, both example perform addition of two number, one add using normal function, the other add remotely.

    Normal Function to Add Two Numbers

    // add two numbers normally

    function add (num1, num2) {

        return num1 + num2;

    }

    const result = add(1, 2); // you get result = 3 immediately

    Async Function to Add Two numbers

    // add two numbers remotely

    // get the result by calling an API

    const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');

    // you get result  = "undefined"

    If you add the numbers with normal function, you get the result immediately. However when you issue a remote call to get result, you need to wait, you can't get the result immediately.

    Or put it this way, you don't know if you will get the result because the server might be down, slow in response, etc. You don't want your entire process to be blocked while waiting for the result.

    Calling APIs, downloading files, reading files are among some of the usual async operations that you'll perform.

    World Before Promises: Callback

    Must we use promise for asynchronous call? Nope. Prior to Promise, we use callback. Callback is just a function you call when you get the return result. Let's modify the previous example to accept a callback.

    // add two numbers remotely

    // get the result by calling an API

    function addAsync (num1, num2, callback) {

        // use the famous jQuery getJSON callback API

        return $.getJSON('http://www.example.com', {

            num1: num1,

            num2: num2

        }, callback);

    }

    addAsync(1, 2, success => {

        // callback

        const result = success; // you get result = 3 here

    });

    The syntax looks ok, why do we need promises then?

    What if You Want to Perform Subsequent Async Action?

    Let's say, instead of just add the numbers one time, we want to add 3 times. In a normal function, we do this:-

    // add two numbers normally

    let resultA, resultB, resultC;

    function add (num1, num2) {

        return num1 + num2;

    }

    resultA = add(1, 2); // you get resultA = 3 immediately

    resultB = add(resultA, 3); // you get resultB = 6 immediately

    resultC = add(resultB, 4); // you get resultC = 10 immediately

    console.log('total' + resultC);

    console.log(resultA, resultB, resultC);

    How it looks like with callbacks?

    // add two numbers remotely

    // get the result by calling an API

    let resultA, resultB, resultC;

    function addAsync (num1, num2, callback) {

        // use the famous jQuery getJSON callback API

        return $.getJSON('http://www.example.com', {

            num1: num1,

            num2: num2

        }, callback);

    }

    addAsync(1, 2, success => {

        // callback 1

        resultA = success; // you get result = 3 here

        addAsync(resultA, 3, success => {

            // callback 2

            resultB = success; // you get result = 6 here

            addAsync(resultB, 4, success => {

                // callback 3

                resultC = success; // you get result = 10 here

                console.log('total' + resultC);

                console.log(resultA, resultB, resultC);

            });

        });

    });

    Demo: https://jsbin.com/barimo/edit?html,js,console

    The syntax is less user friendly. In a nicer term, It looks like a pyramid, but people usually refer this as "callback hell", because the callback nested into another callback. Imagine you have 10 callbacks, your code will nested 10 times!

    Escape From Callback Hell

    Promises come in to rescue. Let's look at the promise version of the same example.

    // add two numbers remotely using observable

    let resultA, resultB, resultC;

    function addAsync(num1, num2) {

        // use ES6 fetch API, which return a promise

        return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)

            .then(x => x.json());

    }

    addAsync(1, 2)

        .then(success => {

            resultA = success;

            return resultA;

        })

        .then(success => addAsync(success, 3))

        .then(success => {

            resultB = success;

            return resultB;

        })

        .then(success => addAsync(success, 4))

        .then(success => {

            resultC = success;

            return resultC;

        })

        .then(success => {

            console.log('total: ' + success)

            console.log(resultA, resultB, resultC)

        });

    Demo: https://jsbin.com/qafane/edit?js,console

    With promises, we flatten the callback with .then. In a way, it looks cleaner because of no callback nesting. Of course, with ES7 async syntax, we can even further enhance this example, but I leave that to you. :)

    New Kid On the Block: Observables

    Before you settle down with promises, there is something that has come about to make it even easier to deal with async data called Observables.

    Observables are lazy event streams which can emit zero or more events, and may or may not finish.source

    Some key differences between promises and observable are:

    Fear not, let look at the same demo written with Observables. In this example, I am using RxJS for the observables.

    let Observable = Rx.Observable;

    let resultA, resultB, resultC;

    function addAsync(num1, num2) {

        // use ES6 fetch API, which return a promise

        const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)

            .then(x => x.json());

        return Observable.fromPromise(promise);

    }

    addAsync(1,2)

      .do(x => resultA = x)

      .flatMap(x => addAsync(x, 3))

      .do(x => resultB = x)

      .flatMap(x => addAsync(x, 4))

      .do(x => resultC = x)

      .subscribe(x => {

        console.log('total: ' + x)

        console.log(resultA, resultB, resultC)

      });

    Demo: https://jsbin.com/dosaviwalu/edit?js,console

    Notes:

    Observables can do more funky stuff easily. For example, delay add function by 3 seconds with just one line of code or retry so you can retry a call a certain number of times.

    ...

    addAsync(1,2)

      .delay(3000) // delay 3 seconds

      .do(x => resultA = x)

      ...

    Well, let's talk about Observables in future post!

    Summary

    Get yourself familiar with callbacks and promises. Understand them and use them. Don't worry about Observables, just yet. All three can factor into your development depending on the situation.

    Here are the demo code for all mom promise to buy phone examples:

    That's it. Hopefully this article smoothen your path to tame the JavaScript promises. Happy coding!

    相关文章

      网友评论

          本文标题:Promise for dummies, for mummies

          本文链接:https://www.haomeiwen.com/subject/ozkjjqtx.html