JavaScript异步编程史:回调函数到Promise到Async/Await

摘要: 异步编程时JavaScript以及Node.js的一大亮点,其中有什么心酸的黑历史呢?javascript

为了保证可读性,本文采用意译而非直译。另外,本文版权归原做者全部,翻译仅用于学习java

回调函数

简单地说,回调函数(callback function)就是给另一个宿主函数作参数的函数。回调函数在宿主函数内执行,执行结果返回给宿主函数。node

// 给click方法作参数的匿名函数就是一个回调函数
$("body").click(function() {
    alert(`clicked on body`);
});
复制代码

是否是很简单呢?编程

如今,咱们来实现一个回调函数,模拟在游戏中得分升级。小程序

// levelOne()是宿主函数,它接收另一个函数做为参数
// levelOne()的第二个参数callback是一个回调函数,它的名字能够任意取,一般命名为callback只是为了易于理解
function levelOne(value, callback) {
    var newScore = value + 5;
    callback(newScore);
}

function startGame() {
    var currentScore = 5;
    console.log('Game Started! Current score is ' + currentScore);
    
    // levelOne()的第二个参数为回调函数
    levelOne(currentScore, function(levelOneReturnedValue) {
        console.log('Level One reached! New score is ' + levelOneReturnedValue);
    });
}

startGame();
复制代码

执行以上代码,控制台输出是这样的:微信小程序

"Game Started! Current score is 5"
"Level One reached! New score is 10"
复制代码

有输出可知,levelOne()内的代码(var newScore = value + 5;)执行以后,才会执行回调函数中的代码(console.log('Level One reached! New score is ' + levelOneReturnedValue);)。promise

可知,回调函数能够在特定代码执行完成以后再执行,这种执行机制在实际编程中很是有用。在执行一些比较耗时的代码时,好比读取文件,不须要阻塞整个代码去等待它完成,而能够继续执行其余代码;而当文件读取完成后,代码中所绑定给文件读取的回调函数会自动执行。bash

可是,当使用多个层级的的回调函数时,状况会变得很是糟糕...下面是代码示例:微信

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();
复制代码

执行以上代码,控制台输出是这样的:异步

"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"
复制代码

levelThree()为levelTwo()的回调函数,而levelTwo()为levelOne()的回调函数。那么正确的执行顺序是:levelOne() > levelTwo() > levelThree()。

若是有10个回调函数嵌套起来呢?是否是看着就有点头疼了!这个问题就是所谓的回调地狱(callback hell)!有没有解法呢?请听下回分解!

Promise

JavaScript从**ES6(即ECMAScript 2015)**开始支持Promise。简单地说,Promise是一个特殊的对象,它能够表示异步操做的成功或者失败,同时返回异步操做的执行结果。

使用Promise构造函数来定义promise:

// 当一切正常时,调用resolve函数;不然调用reject函数
var promise = new Promise(function(resolve, reject) {
    if ( /* everything turned out fine */ )
    {
        resolve("Stuff worked!");
    }
    else
    {
        reject(Error("It broke"));
    }
});
复制代码

咱们将前文陷入回调地狱的例子使用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);
});

// startGame返回的结果传递给了then函数,而后传递给了levelOne函数
startGame.then(levelOne)
    .then(function(result) {
        // result为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);
    });
复制代码

执行以上代码,控制台输出仍是这样的:

"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"
复制代码

回调函数采用了嵌套的方式依次调用levelOne()、levelTwo() 和levelThree(),而Promise使用then将它们连接起来。

相比回调函数而言,Promise代码可读性更高,代码的执行顺序一目了然。

难道Promise就是JavaScript异步编程的终点吗?固然不是!

Async/Await

JavaScript从**ES8(即ECMAScript 2017)**开始支持Async/Await。它让咱们能够采用同步的方式调用Promise函数,提升异步代码的可读性。

本质上,Async/Await只是基于Promise的语法糖,它让咱们能够使用同步的方式写异步代码。可是,不要所以小看Async/Await,使用同步的方式写异步代码其实很是强大。

在定义函数时,在其前面添加一个async关键字,就能够在函数内使用await了。当await一个Promise时,代码会采用非阻塞的方式继续执行下去。当Promise成功resolve了,await语句会正真执行结束,并获取resolve的值。当Promise失败reject了,await语句初会throw一个错误。

咱们再来用async/await来改写以前的例子:

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);
    });
}

// 只有aysnc函数内能够使用await语句
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();
复制代码

执行以上代码,控制台输出依然是这样的:

"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"
复制代码

突然之间,代码的可读性提升了很是多!固然,async/await的神奇之处不止于此。async/await的出错处理很是方便,由于咱们能够把同步代码和异步代码写在同一个try...catch...语句中。async/await代码调试更加方便,使用Promise时,咱们没法设置断点,而async/await代码能够像同步代码同样设置断点。

参考

关于Fundebug

Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了7亿+错误事件,获得了Google、360、金山软件、百姓网等众多知名用户的承认。欢迎免费试用!

版权声明

转载时请注明做者Fundebug以及本文地址:
blog.fundebug.com/2018/07/11/…

相关文章
相关标签/搜索