iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 28
0

上一章節解釋了什麼是同步/非同步的特性,我們也了解什麼是callback function。

接下來討論的議題,建議先了解同步/非同步、callback function,會比較好理解。

假設一個情境,我們執行了ABCD任務,它們都擁有非同步特性:

(function misA() {
    setTimeout(() => {
        console.log('任務A執行完畢');
    }, Math.floor(Math.random() * 5000));
})();
(function misB() {
    setTimeout(() => {
        console.log('任務B執行完畢');
    }, Math.floor(Math.random() * 5000));
})();
(function misC() {
    setTimeout(() => {
        console.log('任務C執行完畢');
    }, Math.floor(Math.random() * 5000));
})();
(function misD() {
    setTimeout(() => {
        console.log('任務D執行完畢');
    }, Math.floor(Math.random() * 5000));
})();

我們加了時間亂數,所以無法確保哪個任務先完成,這部分已經在同步/非同步章節闡述過了。

那今天如果,我們希望ABCD任務,能依序執行呢?

還記得有什麼方式可以讓我們控制任務流程的嗎?

沒錯,就是使用callback function:

function misA(callback) {
    setTimeout(() => {
        console.log('任務A執行完畢');
        callback();
    }, Math.floor(Math.random() * 2000));
};
function misB(callback) {
    setTimeout(() => {
        console.log('任務B執行完畢');
        callback();
    }, Math.floor(Math.random() * 2000));
};
function misC(callback) {
    setTimeout(() => {
        console.log('任務C執行完畢');
        callback();
    }, Math.floor(Math.random() * 2000));
};
function misD() {
    setTimeout(() => {
        console.log('任務D執行完畢');
    }, Math.floor(Math.random() * 2000));
};
misA(() => {
    misB(() => {
        misC(() => {
            misD();
        });
    });
});

上面跟callback function的範例幾乎一樣,只不過我們加了setTimeout( )來模擬非同步的情況。

使用這樣的方式,可以確保每個任務執行完後,再執行呼叫下一個任務callback function。

可是又看到了Callback Hell,即便我們已經使用箭頭函式,任務內容也都是空的,但可讀性依舊很差。

以往當需要處理setTimeout( )、XMLHttpRequest物件,等各種非同步的方式時,就必須得用上述的方式來處理,造成的麻煩顯而易見。

有鑑於此,ES6新增了一個物件「promise」,專門用來解決同步/非同步的問題,其實早在其他的框架或函式庫,有類似promise作法,直到ES6才被納入原生標準之一。

截至目前為止,已經可以在各大主流瀏覽器的新版運作了,但IE,恩….你知道的XD。

promise

https://ithelp.ithome.com.tw/upload/images/20181112/20112573MFnuDagIIQ.png
圖片來源:MDN Promise

首先promise物件一開始會有以下狀態:

  • 擱置(pending):初始狀態,不是 fulfilled 與 rejected。
  • 實現(fulfilled):表示操作成功地完成。
  • 拒絕(rejected):表示操作失敗了。

promise物件一開始是等待狀態(pending),只有兩種執行結果,不是成功(fulfilled)就是失敗(rejected)。

Promise建構子的語法結構:

new Promise((resolve, reject) => {
    statement
})

resolve:執行成功時要呼叫的函式。
reject:執行失敗時要呼叫的函式。
statement:程式主體,非同步處理的部分。

將Promise物件放入函式,並回傳,此函式便具有Promise的功能。

function asyncFunc(value) {
    return new Promise(function (resolve, reject) {
        if (value)
            resolve(value);
        else
            reject(reason);
    });
}

如果輸入的值具有truthy行為,就執行resolve(value);若否則執行reject(reason)。

將非同步的code,封裝在Promise物件內,並利用Promise物件所提供的方法達到流程控制。

下一步要做的是使用Promise物件非常重要的方法,then,then中文是然後,意思是回傳Promise物件並使用then方法來處理promise物件的狀態。

語法如下:

p.then(onFulfilled, onRejected);
p.then((response) => {
    // 執行成功
}, (error) => {
    // 執行失敗
});

then方法,有2個參數可用:

  • onFulfilled是當promise物件執行成功,狀態為實現(fulfilled)呼叫的函式,並有一個傳入值(value)做後續的運算。
  • onRejected是當promise物件執行失敗,狀態為拒絕(rejected)呼叫的函式,一樣有一個傳入值(reason)做後續的處理。

then函式會回傳一個新的promise物件,這樣做的目的是能夠使用方法串接(method chaining),讓多個非同步運算,可以一直串接下去。

我們來做個範例:

function async (value) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (value)
                resolve(value);
            else
                reject('無輸入值');
        }, 500);
    })
}
async ('ABC').then(
    response => {
        console.log(`輸入值:${response}`);
    }, error => {
        console.log(`錯誤:${error}`);
    }
);
async ().then(
    response => {
        console.log(`輸入值:${response}`);
    }, error => {
        console.log(`錯誤:${error}`);
    }
);

https://ithelp.ithome.com.tw/upload/images/20181112/20112573lLzdjQg514.png
輸入字串,由resolve( )函式回傳value('ABC'),這時物件的狀態是實現(fulfilled),呼叫then函式的onFulfilled,為避免搞混,參數使用response,接收value值,輸出結果。

未輸入值,狀態為拒絕(rejected),呼叫呼叫then函式的onRejected,將string('無輸入值')傳遞給error參數。

剛剛的範例還看不出來promise的好處,所以我們再增加一個非同步任務:

async ('ABC').then(
    response => {
        console.log(`第1次輸入值:${response}`);
        return async ('DEF');
    }, error => {
        console.log(`錯誤:${error}`);
    }
).then(
    response => {
        console.log(`第2次輸入值:${response}`);
    }, error => {
        console.log(`錯誤:${error}`);
    }
);

https://ithelp.ithome.com.tw/upload/images/20181112/20112573h04tVoYLCw.png
第一次成功執行後,又再次的呼叫函式,再處理一次,所以我們可以一直使用then()函式做相同的工作,確保非同步任務的執行順序。

then函式的2個參數,分別處理實現(fulfilled)與拒絕(rejected)狀態,不過為了方便方法串接(method chaining),通常我們只在then函式,傳入第一個處理實現(fulfilled)的函式參數。

至於拒絕(rejected)狀態,會使用catch()函式來處理。

今天的議題先到這邊,我們明天再討論catch( )。

總結:
使用Promise與callback function,可以把多個非同步處理,執行流程轉化為有序的排列,並提共更完善的錯誤處理機制。

參考來源:
MDN Promise
從Promise開始的JavaScript異步生活
重新認識 JavaScript: Day 26 同步與非同步


上一篇
sync 同步 & async 非同步
下一篇
Promise catch
系列文
JavaScript Note31

尚未有邦友留言

立即登入留言