JavaScript中的非同步處理機制 — Promise

Yi-Ning
7 min readFeb 17, 2020

--

參考資料:

callback與Promise都是的JavaScript中用來實現非同步的機制。Promise克服了單純只用callback的一些缺點 (例如以下說明的callback hell就是其中一個),但並不是為了用來取代callback。

What is callback hell?

如果我們想要順利的依序執行某些函式,可以像下面的範例一樣,在一個主函式(下面程式碼中的main)裡面,一層一層的callback。但當想要控制的函式數量更多時,多層的callback會形成難以閱讀及除錯的嵌套,這就稱為callback hell。

Promise確保一個非同步函式的結果可以用已被預期的方式處理

Promise的任務,是確保一個非同步函式的結果可以用一個已被預期的方式處理:要不是被履行(resolve/ fullfilled),就是被拒絕 (reject)。當呼叫一個使用Promise的非同步函式時,一個Promise instance會被回傳。當Promise被resolved或是rejected後,它就會被視為已解決。

Promise還有一個好處是,它是一個object。所以如果想要將一個非同步函式的結果傳遞給你的應用程式的其他地方處理,可以將Promise傳遞過去。

以下是一個運用Promise的範例:

首先我們建立一個Promise實例,建構函式的傳入參數值需要一個函式,建構函式的傳入函式稱為 executor(執行者, 執行函式)。這個函式包含有resolvereject回呼 (並不一定要這樣命名,名稱可自訂),reject的reason通常是用 Error 物件。

也因為它與一般的物件實體化的過程不太一樣,所以常會先包到一個函式中,使用時再呼叫這個函式來產生 promise 物件。

接著我們將回傳的Promise指派給一個變數p,呼叫promise的then以及catch處理器。then處理器可以接收兩種回呼:resolve回呼或是reject回呼。不過我們通常用catch來處理reject,它算是個then方法的語法糖。

在語法上,呼叫多個resolve或是reject並不會造成錯誤(如下範例)。不過這麼做沒有意義,因為一旦一個Promise已經有結果了(無論是被履行或是被拒絕),他是不能再被restart的。

var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(‘hello world 1’);
resolve(‘hello world 2’);
resolve(‘hello world 3’);
resolve(‘hello world 4’);
}, 1000);
});
promise.then(function success(data) {
console.log(data); // hello world 1
});

Chained Promise

then方法最後的回傳值是另一個"新的"Promise 物件。這麼做使Promise有一個優點:可以鏈結 (chained)。而這個Promise物件,可以是任何一個你想要的Promise物件:

var promise = job1();promise.then(function(data1) {
console.log(‘data1’, data1);
return job2(); // we call job2 and we return the resulting promise
})
.then(function(data2) { // call then on the result of the first then
console.log(‘data2’, data2);
// By chaining our 2 promises (job1 then job2), job2 is always executed after job1
return ‘Hello world’;
})
.then(function(data3) {
console.log(‘data3’, data3);
});
// We call then on the result of the previous then. The promise here is an auto-resolved promise, and it will pass 'Hello world' in the data. When you are in a then callback, if you return anything but a promise, an auto-resolved promise is created, and this promise will be the result of the then call.
function job1() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(‘result of job 1’);
}, 1000);
});
}
function job2() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(‘result of job 2’);
}, 1000);
});
}

當一個Promise被履行時,他可以立刻呼叫另一個會回傳Promise的函式:

或是像下面這樣,連續傳遞同一個回傳值:

catch之後依然是可以接續then的。在 Promise 的連鎖執行中,就算catcherror,也不會中斷 Promise 連鎖結構,會繼續執行下去,但會影響這個promise物件最終的status。例如以下程式碼中的checkOne(2) promise物件,catcherror後,還是會印出‘done’字串,而它最後的狀態是rejected

幾個在撰寫範例時踩到的小雷:

  1. 如果.then裡面沒有接收resolvedrejected的結果,則就不是非同步操作了。例如在上面例子中的.then(val => console.log(item))若是寫成:.then(console.log(item))item會最先被印出來。
  2. 如果只定義變數ps, 而沒有做接下來的.then處理,會直接噴錯誤:

--

--