Abstract: Asynchronous programming is one of the highlights of JavaScript and Node.js. What’s the sad dark history?

  • 原文: Async programming basics every JS developer should know in 2018
  • Translator: Fundebug

To ensure readability, free translation rather than literal translation is used in this paper. In addition, the copyright of this article belongs to the original author, and translation is for study only

The callback function

Simply put, a callback function is a function that takes arguments to another host function. The callback function executes inside the host function, and the result is returned to the host function.

// The anonymous function that takes an argument to the click method is a callback
$("body").click(function() {
    alert(`clicked on body`);
});
Copy the code

Is it easy?

Now, let’s implement a callback function that simulates scoring upgrades in the game.

// levelOne() is the host function that takes another function as an argument
// levelOne() 's second argument, callback, is a callback function that can be arbitrarily named, usually just for ease of understanding
function levelOne(value, callback)
{
    var newScore = value + 5;
    callback(newScore);
}

function startGame()
{
    var currentScore = 5;
    console.log('Game Started! Current score is ' + currentScore);
    
    // The second argument to levelOne() is the callback function
    levelOne(currentScore, function(levelOneReturnedValue)
    {
        console.log('Level One reached! New score is ' + levelOneReturnedValue);
    });
}

startGame();
Copy the code

After executing the above code, the console output looks like this:

"Game Started! Current score is 5"
"Level One reached! New score is 10"
Copy the code

Var newScore = value + 5; levelOne() The code in the console.log(‘Level One reached! New score is ‘ + levelOneReturnedValue);) .

As you can see, callbacks can be executed after a particular code execution is complete, which is very useful in real-world programming. When executing some time-consuming code, such as reading a file, you don’t need to block the entire code to wait for it to complete, but can continue executing other code; When the file is read, the callback function bound to the file is automatically executed.

However, when using multiple levels of callback functions, things can get really bad… Here is an example of code:

function levelOne(value, callback)
{
    var newScore = value + 5;
    callback(newScore);
}

function levelTwo(value, callback)
{
    var newScore = value + 10;
    callback(newScore);
}

function levelThree(value, callback)
{
    var newScore = value + 30;
    callback(newScore);
}

function startGame()
{
    var currentScore = 5;
    console.log('Game Started! Current score is ' + currentScore);
    levelOne(currentScore, function(levelOneReturnedValue)
    {
        console.log('Level One reached! New score is ' + levelOneReturnedValue);
        levelTwo(levelOneReturnedValue, function(levelTwoReturnedValue)
        {
            console.log('Level Two reached! New score is ' + levelTwoReturnedValue);
            levelThree(levelTwoReturnedValue, function(levelThreeReturnedValue)
            {
                console.log('Level Three reached! New score is ' + levelThreeReturnedValue);
            });
        });
    });

}

startGame();
Copy the code

After executing the above code, the console output looks like this:

"Game Started! Current score is 5"
"Level One reached! New score is 10"
"Level Two reached! New score is 20"
"Level Three reached! New score is 50"
Copy the code

LevelThree () is the callback to levelTwo(), and levelTwo() is the callback to levelOne(). The correct sequence is levelOne() > levelTwo() > levelThree().

What if there were 10 nested callback functions? Is not looking at a bit of a headache! This problem is called callback hell! Is there a solution? Join us next time!

Promise

JavaScript supports Promises from **ES6(i.e. ECMAScript 2015)**. Simply put, a Promise is a special object that represents the success or failure of an asynchronous operation and returns the result of the execution of the asynchronous operation.

Use the Promise constructor to define promises:

// When all is well, call resolve; Otherwise, the reject function is called
var promise = new Promise(function(resolve, reject)
{
    if ( /* everything turned out fine */ )
    {
        resolve("Stuff worked!");
    }
    else
    {
        reject(Error("It broke")); }});Copy the code

We’ll rewrite the previous example of falling into callback hell with Promise:

function levelOne(value)
{
    var promise, newScore = value + 5;
    return promise = new Promise(function(resolve)
    {
        resolve(newScore);
    });
}

function levelTwo(value)
{
    var promise, newScore = value + 10;
    return promise = new Promise(function(resolve)
    {
        resolve(newScore);
    });
}

function levelThree(value)
{
    var promise, newScore = value + 30;
    return promise = new Promise(function(resolve)
    {
        resolve(newScore);
    });
}

var startGame = new Promise(function(resolve, reject)
{
    var currentScore = 5;
    console.log('Game Started! Current score is ' + currentScore);
    resolve(currentScore);
});

The result returned by startGame is passed to the then function, which is then passed to the levelOne function
startGame.then(levelOne)
    .then(function(result)
    {
        // result is the return value of levelOne
        console.log('You have reached Level One! New score is ' + result);
        return result;
    })
    .then(levelTwo)
    .then(function(result)
    {
        console.log('You have reached Level Two! New score is ' + result);
        return result;
    })
    .then(levelThree)
    .then(function(result)
    {
        console.log('You have reached Level Three! New score is ' + result);
    });
Copy the code

After executing the above code, the console output looks like this:

"Game Started! Current score is 5"
"Level One reached! New score is 10"
"Level Two reached! New score is 20"
"Level Three reached! New score is 50"
Copy the code

The callback calls levelOne(), levelTwo(), and levelThree() in a nested fashion, and Promise links them together using then.

The Promise code is much more readable than the callback function, and the order of execution is straightforward.

Is Promise the end of asynchronous JavaScript programming? Of course not!

Async/Await

JavaScript supports Async/Await as of **ES8(i.e. ECMAScript 2017)**. It allows us to call the Promise function synchronously, improving the readability of our asynchronous code.

In essence Async/Await is just syntactic candy based on Promise that allows us to write asynchronous code in a synchronous manner. But don’t underestimate Async/Await, writing asynchronous code in a synchronous manner is very powerful.

When defining a function, add an async keyword in front of it to use await inside the function. When await a Promise, the code continues execution in a non-blocking manner. When the Promise succeeds in resolve, the await statement terminates exactly and takes the value of resolve. When a Promise fails reject, the await statement initially throws an error.

Let’s rewrite the previous example with async/await again:

function levelOne(value)
{
    var promise, newScore = value + 5;
    return promise = new Promise(function(resolve)
    {
        resolve(newScore);
    });
}

function levelTwo(value)
{
    var promise, newScore = value + 10;
    return promise = new Promise(function(resolve)
    {
        resolve(newScore);
    });
}

function levelThree(value)
{
    var promise, newScore = value + 30;
    return promise = new Promise(function(resolve)
    {
        resolve(newScore);
    });
}

// Only aysnc functions can use await statements
async function startGame()
{
    var currentScore = 5;
    console.log('Game Started! Current score is ' + currentScore);
    currentScore = await levelOne(currentScore);
    console.log('You have reached Level One! New score is ' + currentScore);
    currentScore = await levelTwo(currentScore);
    console.log('You have reached Level Two! New score is ' + currentScore);
    currentScore = await levelThree(currentScore);
    console.log('You have reached Level Three! New score is ' + currentScore);
}

startGame();
Copy the code

After executing the above code, the console output still looks like this:

"Game Started! Current score is 5"
"Level One reached! New score is 10"
"Level Two reached! New score is 20"
"Level Three reached! New score is 50"
Copy the code

All of a sudden, the readability of the code improved dramatically! Of course, the magic of async/await doesn’t stop there. Error handling of async/await is very convenient because we can write synchronous and asynchronous code in the same try… catch… In the statement. Debugging async/await code is more convenient, with promises we cannot set breakpoints, whereas async/await code can set breakpoints just like synchronous code.

reference

  • Refactoring: From Promise to Async/Await
  • Async/Await simplifies JavaScript code in this way
  • 6 reasons Async/Await should replace Promise

About Fundebug

Fundebug focuses on JavaScript, wechat applets, wechat mini games, Alipay applets, React Native, Node.js and Java real-time BUG monitoring. Since its official launch on November 11, 2016, Fundebug has handled more than 700 million error events in total, which has been recognized by many well-known users such as Google, 360, Kingsoft, and People’s Net. Welcome free trial!

Copyright statement

Reprint please indicate the author Fundebug and this article addresses: blog.fundebug.com/2018/07/11/…