JavaScript異步編程史:回調函數到Promise到Async/Await

摘要: 異步編程時JavaScript以及Node.js的一大亮點,其中有什麼心酸的黑歷史呢?javascript

<!-- more -->java

爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習node

小編推薦:Fundebug專一於JavaScript、微信小程序、微信小遊戲,Node.js和Java線上bug實時監控。真的是一個很好用的bug監控服務,衆多大佬公司都在使用。編程

回調函數

簡單地說,回調函數(callback function)就是給另一個宿主函數作參數的函數。回調函數在宿主函數內執行,執行結果返回給宿主函數。小程序

// 給click方法作參數的匿名函數就是一個回調函數
$("body").click(function() {
    alert(`clicked on body`);
});

是否是很簡單呢?微信小程序

如今,咱們來實現一個回調函數,模擬在遊戲中得分升級。promise

// 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);)。異步

可知,回調函數能夠在特定代碼執行完成以後再執行,這種執行機制在實際編程中很是有用。在執行一些比較耗時的代碼時,好比讀取文件,不須要阻塞整個代碼去等待它完成,而能夠繼續執行其餘代碼;而當文件讀取完成後,代碼中所綁定給文件讀取的回調函數會自動執行。async

可是,當使用多個層級的的回調函數時,狀況會變得很是糟糕...下面是代碼示例:

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代碼能夠像同步代碼同樣設置斷點。

參考

相關文章
相關標籤/搜索