JS Promise and Async internals

SAMPLE CODE: https://github.com/tkhemani/TSCode/commit/e564cc73b4fb92de8e6bc0ef074ad837cdf31adb

TL;DR: With Typescript if you’re using “target”: “es2016” then LIB is not needed, but if you’re using 

“target”: “es5”, then you need “lib”: [“es6”] in tsconfig for Promise etc. to work

ref: https://basarat.gitbooks.io/typescript/content/docs/types/lib.d.ts.html

  • The entire foundation for async/await is promises. In fact every async function you write will return a promise, and every single thing you await will ordinarily be a promise.
  • A promise is a special kind of javascript object which contains another object. I could have a promise for the integer 17, or the string “hello world”, or some arbitrary object, or anything else you could normally store in a javascript variable.

    How do I access the data in a promise? I use `.then()`:

    function getFirstUser() {
        return getUsers().then(function(users) {
            return users[0].name;
        });
    }

    How do I catch the errors from a promise chain? I use `.catch()`

    function getFirstUser() {
        return getUsers().then(function(users) {
            return users[0].name;
        }).catch(function(err) {
            return {
              name: 'default user'
            };
        });
    }

    Even though promises will usually be for ‘future’ data, once I actually have a promise for something, I really don’t need to care whether the data will be there in future, or it’s already been retrieved. I just call `then()` in either case.

  • Any promise we have, using ES2016, we can await. That’s literally all await means: it functions in exactly the same way as calling `.then()` on a promise (but without requiring any callback function). So the above code becomes:

    async function getFirstUser() {
        let users = await getUsers();
        return users[0].name;
    }

    I can await any promise I want, whether it’s already been resolved or not, whether I created it or not. await will simply pause the execution of my method until the value from the promise is available.

    So… what about catching errors?

    Simple, now we’re writing synchronous style code, we can go back to using try/catch:

    async function getFirstUser() {
        try {
            let users = await getUsers();
            return users[0].name;
        } catch (err) {
            return {
                name: 'default user'
            };
        }
    }

    Alright, cool. So there’s a way of writing it with promises, and a way of writing it with async/await.

  • Awaiting Multiple Promises: 
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);

    This is kinda confusing — aren’t we using async/await, not promises?! The reason this works because async/await and promises are the same thing under the hood.

    It’s far easier to get a mental grasp of this if you understand what Promise.all means, so let’s go back to promise basics. Fundamentally, Promise.all will take an array of promises, and compose them all into a single promise, which resolves only when every child promise in the array has resolved itself.

    Then, in the above example, we’re awaiting that ‘super promise’ we created using Promise.all.

  • Converting Promise to Callback

    Treating the result of an async function as a promise, which itself accepts callbacks. Consider the following:

    function getFirstUser(callback) {
        return getUsers().then(function(user) {
            return callback(null, users[0].name);
        }).catch(function(err) {
            return callback(err);
        });
    }

    Cool, I just converted an async function (getUsers) into a callback response, without a huge amount of boilerplate. In fact, a lot of promise libraries even do this for you with a `nodeify()` method, like:

    function getFirstUser(callback) {
        return getUsers().then(function(user) {
            return users[0].name;
        }).nodeify(callback);
    }
  • Converting Callback to Promise:

    What if I have an async function and I need to call some callback code from within it?

    Again, it helps to understand promises here, because really the only way is to transform my callback method into a promise returning method—and in ES6, converting a callback to a promise is pretty easy:

    function callbackToPromise(method, ...args) {
        return new Promise(function(resolve, reject) {
            return method(...args, function(err, result) {
                return err ? reject(err) : resolve(result);
            });
        });
    }

    Then:

    async function getFirstUser() {
        let users = await callbackToPromise(getUsers);
        return users[0].name;
    }

 

ref: https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8#.eflo3c43t

Methods

Promise.all(iterable)
Returns a promise that either fulfills when all of the promises in the iterable argument have fulfilled or rejects as soon as one of the promises in the iterable argument rejects. If the returned promise fulfills, it is fulfilled with an array of the values from the fulfilled promises in same order as defined in the iterable. If the returned promise rejects, it is rejected with the reason from the first promise in the iterable that rejected. This method can be useful for aggregating results of multiple promises.
Promise.race(iterable)
Returns a promise that fulfills or rejects as soon as one of the promises in the iterable fulfills or rejects, with the value or reason from that promise.
Promise.reject(reason)
Returns a Promise object that is rejected with the given reason.
Promise.resolve(value)
Returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will “follow” that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value. Generally, if you don’t know if a value is a promise or not, Promise.resolve(value) it instead and work with the return value as a promise.

 

Promise.prototype.catch(onRejected)
Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
Promise.prototype.then(onFulfilled, onRejected)
Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler, or to its original settled value if the promise was not handled (i.e. if the relevant handler onFulfilled or onRejected is not a function).

 

Edge Case

The rule is, if the function that is in the then handler returns a value, the promise resolves/rejects with that value, and if the function returns a promise, what happens is, the next then clause will be the then clause of the promise the function returned, so, in this case, the first example falls through the normal sequence of the thens and prints out values as one might expect, in the second example, the promise object that gets returned when you do Promise.resolve("bbb")‘s then is the then that gets invoked when chaining(for all intents and purposes). The way it actually works is described below in more detail.

Quoting from the Promises/A+ spec:

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant then method. It also allows Promises/A+ implementations to “assimilate” nonconformant implementations with reasonable then methods.

The key thing to notice here is this line:

if x is a promise, adopt its state [3.4]

link: https://promisesaplus.com/#point-49

This is the difference between:

new Promise(function(res,rej) {
    res("aaa");
})
.then(function(result) {
    return "bbb";
})
.then(function(result) {
    console.log(result);
});

and this:

new Promise(function(res,rej) {
    res("aaa");
})
.then(function(result) {
    return Promise.resolve("bbb");
})
.then(function(result) {
    console.log(result);
});

Examples

Super Simple (10 lines!)

var myFirstPromise = new Promise(function(resolve, reject){
    //We call resolve(...) when what we were doing async succeeded, and reject(...) when it failed.
    //In this example, we use setTimeout(...) to simulate async code. 
    //In reality, you will probabally using something like XHR or an HTML5 API.
    setTimeout(function(){
        resolve("Success!"); //Yay! Everything went well!
    }, 250);
});

myFirstPromise.then(function(successMessage){
    //successMessage is whatever we passed in the resolve(...) function above.
    //It doesn't have to be a string, but if it is only a succeed message, it probably will be.
    console.log("Yay! " + successMessage);
});

With Async Await: 

let a = new Promise((resolve, reject) => {
    resolve(10);
})
async function iife() {
    let result = await a;
    console.log(result);
}
iife();
 
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s