ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Promises, Promisification, Async, Await, and Ajax
    DynamicPL/Javascript 2019. 10. 23. 10:42

    1. Overview

    The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. But syntax to consume promise can still be quite confusing and difficult to manage. And so in ES8 or ES2017, something called Async/Await was introduced to JavaScript in order to make it a lot easier for developers to consume promises.

    2. Motivation

    2.1 Callback hell

    Unmanagable nested chaining of a callback function.

    function getRecipe() {
                setTimeout(() => {
                    const recipeID = [523, 883, 432, 974];
                    console.log(recipeID);
    
                    setTimeout(id => {
                        const recipe = {title: 'Fresh tomato pasta', publisher: 'Jonas'};
                        console.log(`${id}: ${recipe.title}`);
    
                        setTimeout(publisher => {
                            const recipe2 = {title: 'Italian Pizza', publisher: 'Jonas'};
                            console.log(recipe);
                        }, 1500, recipe.publisher);
    
                    }, 1500, recipeID[2]);
    
                }, 1500);
            }
            getRecipe();

    3. Promise

    3.1 Definition

    • An object that keeps track of whether a certain event has happened already or not.
    • Determines what happen after the event has happened
    • Implements the concept of future value that we're expecting

    3.2 Promise states

    3.3 Implement

    Passing a function to Promise args which is called immediately after promise created. This function is named executor function which takes two arguments which are callback function's resolve and reject. Usually, the executor function is an asynchronous function.

    const getIDs = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve([523, 883, 432, 974]);
                }, 1500);
            });
            
    const getRecipe = recID => {
                return new Promise((resolve, reject) => {
                    setTimeout(ID => {
                        const recipe = {title: 'Fresh tomato pasta', publisher: 'Jonas'};
                        resolve(`${ID}: ${recipe.title}`);
                    }, 1500, recID);
                });
            };
    
    const getRelated = publisher => {
                return new Promise((resolve, reject) => {
                    setTimeout(pub => {
                        const recipe = {title: 'Italian Pizza', publisher: 'Jonas'};
                        resolve(`${pub}: ${recipe.title}`);
                    }, 1500, publisher);
                });
            };

    The executor function is used to inform the promise whether the event it is handling was successful or not. And if it was successful we're gonna call the resolve function and if not we call the reject function. We had these two states which resolve if the promise was successful and reject if the promise was not successful.

    We call resolve in case that event was successful. So calling the resolve function will basically mark the promise as fulfilled. Resolve function actually takes arguments that are for the result of the promise. So this is basically how we return our result from the promise if it was successful.

    Now it is actually time that we consume this promise, and in order to do that, we use two methods on all of the promise objects which are then and catch. So all of the promise objects inherit these two methods.

    getIDs
            .then(IDs => {
                console.log(IDs);
                return getRecipe(IDs[2]);
            })
            .then(recipe => {
                console.log(recipe);
                return getRelated('Jonas Schmedtmann');
            })
            .then(recipe => {
                console.log(recipe);
            })
            .catch(error => {
                console.log('Error!!');
            });

    This then method allows us to add an event handler for the case that the promise is fulfilled which means that there is a result. So all we have to do is to pass in a callback function. And arguments of this callback function will be results of the successful promise.

    We also have access to another method which is the catch method which allows us to add a handler for the case that a promise gets rejected. So then an error happened

    Instead of nesting of calling another callback function inside of previous then function, just return a promise from this then method and cascading then function after previous then function which is out of the previous then function.

    This is the beauty of Promise which separates a sequence of callback functions with the associated state.

    4. Promisification

    Promisification is the conversion of a function that accepts a callback into a function that returns a promise. Such transformations are often required in real-life, as many functions and libraries are callback-based. But promises are more convenient, so it makes sense to promisify them.

    4.1 Example

    function loadScript(src, callback) {
      let script = document.createElement('script');
      script.src = src;
    
      script.onload = () => callback(null, script);
      script.onerror = () => callback(new Error(`Script load error for ${src}`));
    
      document.head.append(script);
    }
    
    // usage:
    // loadScript('path/script.js', (err, script) => {...})

    4.2 Promisify

    Let’s promisify it. The new loadScriptPromise(src) function achieves the same result, but it accepts only src (no callback) and returns a promise.

    let loadScriptPromise = function(src) {
      return new Promise((resolve, reject) => {
        loadScript(src, (err, script) => {
          if (err) reject(err)
          else resolve(script);
        });
      })
    }
    
    // usage:
    // loadScriptPromise('path/script.js').then(...)

    4.3 Helper function (Wrapper function)

    As we can see, it delegates all the work to the original loadScript, providing its own callback that translates to promise resolve/reject.

    In practice, we’ll probably need to promisify many functions, so it makes sense to use a helper. We’ll call it promisify(f): it accepts a to-promisify function f and returns a wrapper function.

    That wrapper does the same as in the code above: returns a promise and passes the call to the original f, tracking the result in a custom callback:

    function promisify(f) {
      return function (...args) { // return a wrapper-function
        return new Promise((resolve, reject) => {
          function callback(err, result) { // our custom callback for f
            if (err) {
              reject(err);
            } else {
              resolve(result);
            }
          }
    
          args.push(callback); // append our custom callback to the end of f arguments
    
          f.call(this, ...args); // call the original function
        });
      };
    };
    
    // usage:
    let loadScriptPromise = promisify(loadScript);
    loadScriptPromise(...).then(...);

    Here’s a more advanced version of promisify: if called as promisify(f, true), the promise result will be an array of callback results [res1, res2, ...]:

    // promisify(f, true) to get array of results
    function promisify(f, manyArgs = false) {
      return function (...args) {
        return new Promise((resolve, reject) => {
          function callback(err, ...results) { // our custom callback for f
            if (err) {
              reject(err);
            } else {
              // resolve with all callback results if manyArgs is specified
              resolve(manyArgs ? results : results[0]);
            }
          }
    
          args.push(callback);
    
          f.call(this, ...args);
        });
      };
    };
    
    // usage:
    f = promisify(f, true);
    f(...).then(arrayOfResults => ..., err => ...)

    There are also modules with a bit more flexible promisification functions, e.g. es6-promisify. In Node.js, there’s a built-in util.promisify function for that.

    4.4 es6-promisify

    const {promisify} = require("es6-promisify");
    
    // Convert the stat function
    const fs = require("fs");
    const stat = promisify(fs.stat);
    
    // Now usable as a promise!
    stat("example.txt").then(function (stats) {
        console.log("Got stats", stats);
    }).catch(function (err) {
        console.error("Yikes!", err);
    });

    4.4.1 Promisify methods

    const {promisify} = require("es6-promisify");
    
    // Create a promise-based version of send_command
    const redis = require("redis").createClient(6379, "localhost");
    const client = promisify(redis.send_command.bind(redis));
    
    // Send commands to redis and get a promise back
    client("ping").then(function (pong) {
        console.log("Got", pong);
    }).catch(function (err) {
        console.error("Unexpected error", err);
    }).then(function () {
        redis.quit();
    });

    4.4.2 Handle multiple callback arguments, with named parameters

    const {promisify} = require("es6-promisify");
    
    function test(cb) {
        return cb(undefined, 1, 2, 3);
    }
    
    // Create promise-based version of test
    test[promisify.argumentNames] = ["one", "two", "three"];
    const multi = promisify(test);
    
    // Returns named arguments
    multi().then(result => {
        console.log(result); // {one: 1, two: 2, three: 3}
    });

    4.4.3 Provide your own Promise implementation

    const {promisify} = require("es6-promisify");
    
    // Now uses Bluebird
    promisify.Promise = require("bluebird");
    
    const test = promisify(cb => cb(undefined, "test"));
    test().then(result => {
        console.log(result); // "test", resolved using Bluebird
    });

    5. Async/Await

    async prefixed function means it's an async function that basically keeps running in the background and returns a promise. What's important is that inside an Async function, we can have one on more Await expressions. And what happens here is that each await expression will really stop the code from executing until the promise is fulfilled. And if Promise is resolved then the value of the await expression is resolved value of the promise which is then assigned to each variable.

    async function getRecipesAW() {
                const IDs = await getIDs;
                console.log(IDs);
                const recipe = await getRecipe(IDs[2]);
                console.log(recipe);
                const related = await getRelated('Jonas Schmedtmann');
                console.log(related);
    
                return recipe;
            }
    
    // This not working.
    // const rec = getRecipesAW()
    // console.log(rec)
    getRecipesAW().then(result => console.log(`${result} is the best ever!`));

    It allows us to consume Promises but without all the callbacks that we have with callback hell and to some degree, also in promises, with the ten and catch methods.

    The await expression can only be used inside an async function which runs in the background. So we can never happen the main code stopping. So stopping function inside an async function is not a problem because it just runs in the background.

    Also, return from async functions and assign it to a variable that runs synchronously like the above comments, is not working. Because async function always returns Promise.

    And you can handle errors using try/catch statements like synchronous code in an async function.

    async function getWeatherAW(woeid) {
                try {
                    const result = await fetch(`https://crossorigin.me/https://www.metaweather.com/api/location/${woeid}/`);
                    const data = await result.json();
                    const tomorrow = data.consolidated_weather[1];
                    console.log(`Temperatures tomorrow in ${data.title} stay between ${tomorrow.min_temp} and ${tomorrow.max_temp}.`);
                    return data;
                } catch(error) {
                    alert(error);
                }
            }
      
     getWeatherAW(2487956);

    5.1 Pausing code execution

    You can pause the execution in a non-blocking way using async / await. For example:

    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms))
    }
    
    async function main() {
      await sleep(1500)
      console.log('Hello world with delay!')
    }
    
    main()

    6. Asynchronous Javascript And XML(AJAX) and API

    7. References

    developers.google.com/web/fundamentals/primers/async-functions

    developers.google.com/web/fundamentals/primers/promises?hl=ko#promise-api-reference

    developers.google.com/web/fundamentals/primers/promises?hl=ko

    javascript.info/async-await

    medium.com/@constell99/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-async-await-%EA%B0%80-promises%EB%A5%BC-%EC%82%AC%EB%9D%BC%EC%A7%80%EA%B2%8C-%EB%A7%8C%EB%93%A4-%EC%88%98-%EC%9E%88%EB%8A%94-6%EA%B0%80%EC%A7%80-%EC%9D%B4%EC%9C%A0-c5fe0add656c

    https://blog.risingstack.com/mastering-async-await-in-nodejs/#:~:text=Async%20functions%20are%20available%20natively,used%20in%20the%20global%20scope.

    https://github.com/digitaldesignlabs/es6-promisify

    https://codingheroes.io/

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

    stackoverflow.com/questions/41475970/control-which-code-stops-with-async-await

    'DynamicPL > Javascript' 카테고리의 다른 글

    First-Class Functions and IIFE  (0) 2019.10.24
    Primitives vs. Objects  (0) 2019.10.24
    Classes and subclasses  (0) 2019.10.23
    Event Loop, Execution Stack, Web APIs, and Message Queue  (0) 2019.10.23
    Array Operations  (0) 2019.09.22

    댓글

Designed by Tistory.