使用Await減小回調嵌套

在開發的時候,有時候須要發不少請求,而後常常會面臨嵌套回調的問題,即在一個回調裏面又嵌了一個回調,致使代碼層層縮進得很厲害,以下代碼所示:javascript

ajax({
    url: "/list",
    type: "GET",
    success: function(data) {
       appendToDOM(data);
        ajax({
            url: "/update",
            type: "POST",
            success: function(data) {
                util.toast("Success!");
            })
        });
    }
});複製代碼

這樣的代碼看起來有點吃力,這種異步回調一般能夠用Promise優化一下,能夠把上面代碼改爲:java

new Promise(resolve => {
    ajax({
        url: "/list",
        type: "GET",
        success: data => resolve(data);
    })
}).then(data => {
   appendToDOM(data);
    ajax({
        url: "/update",
        type: "POST",
        success: function(data) {
            util.toast("Successfully!");
        })  
    }); 
});複製代碼

Promise提供了一個resolve,方便通知何時異步結束了,不過本質仍是同樣的,仍是使用回調,只是這個回調放在了then裏面。ajax


當須要獲取屢次異步數據的時候,可使用Promise.all解決:npm

let orderPromise = new Promise(resolve => {
    ajax("/order", "GET", data => resolve(data));
});
let userPromise = new Promise(resolve => {
    ajax("/user", "GET", data => resolve(data));
});

Promise.all([orderPromise, userPromise]).then(values => {
    let order = values[0],
         user = values[1];
});複製代碼

可是這裏也是使用了回調,有沒有比較優雅的解決方式呢?json


ES7的await/async可讓異步回調的寫法跟寫同步代碼同樣。第一個嵌套回調的例子能夠用await改爲下面的代碼:babel

// 使用await獲取異步數據
let leadList = await new Promise(resolve => {
    ajax({
        url: "/list",
        type: "GET",
        success: data => resolve(data);
    });
});

// await讓代碼很天然地像瀑布流同樣寫下來 
appendToDom(leadList);
ajax({
    url: "/update",
    type: "POST",
    success: () => util.toast("Successfully");
});
複製代碼

Await讓代碼能夠像瀑布流同樣很天然地寫下來。app

第二個例子:獲取屢次異步數據,能夠改爲這樣:異步

let order = await new Promise(
           resolve => ajax("/order", data => resovle(data))),

    user = await new Promise(
           resolve => ajax("/user", data => resolve(data)));

// do sth. with order/user複製代碼

這種寫法就好像從本地獲取數據同樣,就不用套回調函數了。async

Await除了用在發請求以外,還適用於其它異步場景,例如我在建立訂單前先彈一個小框詢問用戶是要建立哪一種類型的訂單,而後再彈具體的設置訂單的框,因此按正常思路這裏須要傳遞一個按鈕回調的點擊函數,以下圖所示:函數

但其實可使用await解決,以下代碼所示:

let quoteHandler = require("./quote");
// 彈出框詢問用戶並獲得用戶的選擇
let createType = await quoteHandler.confirmCreate();複製代碼

quote裏面返回一個Promise,監聽點擊事件,並傳遞createType:

let quoteHandler = {
    confirmCreate: function(){
        dialog.showDialog({
            contentTpl: tpl,
            className: "confirm-create-quote"
        });
        let $quoteDialog = $(".confirm-create-quote form")[0];
        return new Promise(resolve => {
            $(form.submit).on("click", function(event){
                resolve(form.createType.value);
            });
        });
    }

}
複製代碼

這樣外部調用者就可使用await,而不用傳遞一個點擊事件的回調函數了。

可是須要注意的是await的一次性執行特色。相對於回調函數來講,await的執行是一次性的,例如監聽點擊事件,而後使用await,那麼點擊事件只會執行一次,由於代碼從上往下執行完了,因此當但願點擊以後出錯了還能繼續修改和提交就不能使用await,另外使用await獲取異步數據,若是出錯了,那麼成功的resolve就不會執行,後續的代碼也不會執行,因此請求出錯的時候基本邏輯不會有問題。


要在babel裏面使用await,須要:

(1)安裝一個Node包

npm install --save-dev babel-plugin-transform-async-to-generator

(2)在工程的根目錄添加一個.babelrc文件,內容爲:

{
  "plugins": ["transform-async-to-generator"]
}
複製代碼

(3)使用的時候先引入一個模塊

require("babel-polyfill");複製代碼

而後就能夠愉快地使用ES7的await了。

使用await的函數前面須要加上async關鍵字,以下代碼:

async showOrderDialog() {
     // 獲取建立類型
     let createType = await quoteHandler.confirmCreate();

     // 獲取老訂單數據 
     let orderInfo = await orderHandler.getOrderData();
}
複製代碼


咱們再舉一個例子:使用await實現JS版的sleep函數,由於原生是沒有提供線程休眠函數的,以下代碼所示:

function sleep (time) {
    return new Promise(resolve => 
                          setTimeout(() => resolve(), time));
}

async function start () {
    await sleep(1000);
}

start();
複製代碼


babel的await實現是轉成了ES6的generator,以下關鍵代碼:

while (1) {
    switch (_context.prev = _context.next) {
        case 0:
            _context.next = 2;
            // sleep返回一個Promise對象
            return sleep(1000);

        case 2:
        case "end":     
            return _context.stop();
    }
}複製代碼

而babel的generator也是要用ES5實現的,什麼是generator呢?以下圖所示:

生成器用function*定義,每次執行生成器的next函數的時候會返回當前生成器裏用yield返回的值,而後生成器的迭代器日後走一步,直到全部yield完了。

有興趣的能夠繼續研究babel是如何把ES7轉成ES5的,聽說原生的實現仍是直接基於Promise.


使用await還有一個好處,能夠直接try-catch捕獲異步過程拋出的異常,由於咱們是不能直接捕獲異步回調裏面的異常的,以下代碼:

let quoteHandler = {
    confirmCreate: function(){
        $(form.submit).on("click", function(event){
            // 這裏會拋undefined異常:訪問了undefined的value屬性
            callback(form.notFoundInput.value);
        });
    }
}

try {
    // 這裏沒法捕獲到異常
    quoteHandler.confirmCreate();
} catch (e) {

}複製代碼

上面的try-catch是沒有辦法捕獲到異常的,由於try裏的代碼已經執行完了,在它執行的過程當中並無異常,所以沒法在這裏捕獲,若是使用Promise的話通常是使用Promise鏈的catch:

let quoteHandler = {
    confirmCreate: function(){
        return new Promise(resolve => {
            $(form.submit).on("click", function(event){
                // 這裏會拋undefined異常:訪問了undefined的value屬性
                resolve(form.notFoundInput.value);
            });
        });
    }
}

quoteHandler.confirmCreate().then(createType => {

}).catch(e => {
    // 這裏能捕獲異常
});複製代碼

而使用await,咱們能夠直接用同步的catch,就好像它真的變成同步執行了:

try {
    createType = await quoteHandler.confirmCreate("order");
}catch(e){
    console.log(e);
    return;
}複製代碼


總之使用await讓代碼少寫了不少嵌套,很方便的邏輯處理,縱享絲滑。

相關文章
相關標籤/搜索