[JS] ES6 - Promise (2)

前言

繼續之前 ES6 - Promise(1),繼續來研究 Promise,今天要來研究的是 Promise 的 then 方法。

語法

then 方法回傳一個 Promise。它接收兩個參數: Promise 在成功及失敗情況時的回呼函式。

1
2
3
4
5
6
7
p.then(onFulfilled[, onRejected]);

p.then(function(value) {
// fulfillment
}, function(reason) {
// rejection
});

參數

  • onFulfilled
    一個函式,當 Promise 被實現(fulfilled)時被呼叫。此函式接收一個 fullfillment value 作為參數。
  • onRejected (optional)
    一個函式,當 Promise 被拒絕(rejected)時被呼叫。此函式接收一個 rejection reason 作為參數。

回傳值

當 Promise 在 pending 的狀態的時候。then 的 handler function (onFulfilled 或 onRejected)會非同步地被呼叫,
MDN 上關於被調用後,handler function 回傳的值有五種情況,詳細的敘述可以參考 MDN,這裡不多作解釋,直接看範例會比較容易理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var resolvedProm = Promise.resolve(33);
// 使用一個已實現的 promise,'then' 區塊將立即被觸發,但是它的 handlers 將是非同步地被觸發,如同 console.logs 結果所示
var thenProm = resolvedProm.then(function(value){
console.log("this gets called after the end of the main stack. the value received and returned is: " + value);
return value;
});
// 立即紀錄 thenProm
console.log("second" + thenProm);

// 使用 setTimeout 延遲(postpone)函式執行直到 stack 為空
setTimeout(function(){
console.log("third" + thenProm);
});

// 結果依序為
// second Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// "this gets called after the end of the main stack. the value received and returned is: 33"
// third Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}

其他

then()catch() 方法都會回傳 Promise,所以可以串接在一起。

範例

then() 的使用

1
2
3
4
5
6
7
8
9
10
11
var p1 = new Promise( (resolve, reject) => {
resolve('Success!');
// or
// reject ("Error!");
} );

p1.then( value => {
console.log(value); // Success! (Promise 被實現的時候會執行)
}, reason => {
console.log(reason); // Error! (Promise 被拒絕的時候會執行)
} );

then() 的串接

使用 setTimeout 來模擬非同步的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Promise.resolve('foo')
// 1. Receive "foo", concatenate "bar" to it, and resolve that to the next then
.then(function(string) {
console.log('before return Promise');
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. receive "foobar", register a callback function to work on that string
// and print it to the console, but not before returning the unworked on
// string to the next then
.then(function(string) {
console.log('before setTimeout');
setTimeout(function() {
string += 'baz';
console.log(string);
}, 1)
return string;
})
// 3. print helpful messages about how the code in this section will be run
// before the string is actually processed by the mocked asynchronous code in the
// previous then block.
.then(function(string) {
console.log("Last Then: oops... didn't bother to instantiate and return " +
"a promise in the prior then so the sequence may be a bit " +
"surprising");

// Note that `string` will not have the 'baz' bit of it at this point. This
// is because we mocked that to happen asynchronously with a setTimeout function
console.log('last'+ string);
});

依序回傳的順序是:

1
2
3
4
5
"before return Promise"
"before setTimeout"
"Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising"
"lastfoobar"
"foobarbaz"

一開始的 Promise.resolve() 靜態設定 Promise 已經是實現的狀態,會直接回傳 resolve 的值。並執行第一個 then(),所以先印出 “before return Promise”,接著回傳一個新的 Promise,這裡因為設計了一個 setTimeout 所以會有時間間隔,當它還在等待並執行 new Promise 的 statement 的時候,javascript 會先繼續向下執行下一個 then(),印出 “before setTimeout”。但是該 then() 的 handler 又有一個 setTimeout 函示在等著執行,所以接著進入最後一個 then(),印出那一長串文字。在這同時第一個 then() 回傳的 promise 已經執行完畢並 resolve string = lastfoobar,所以會接著印出 “lastfoobar”。最後才會印出 “foobarbaz”。
為什麼最後一個 then() 印出的文字沒有 “baz” ? 這是因為透過 setTimeout 還模擬非同步的情況,第一個 then() 回傳 string 的時候,當第二個 then() 還沒有執行完的時候就先傳到了第三個 then(),所以第三個 then() 回傳的文字不帶會有第二個 then() 回傳的值。

如果我們把 setTimeout 函示拿掉,回傳的順序會變成

1
2
3
4
5
"before return Promise"
"before setTimeout"
"foobarbaz"
"Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising"
"lastfoobarbaz"

兩種結果互相比較,就可以更加了解 then() 在非同步情況下的執行方式。

使用 then() 回傳一個 reject 的 promise

因為 then() 回傳一個 reject 的 promise,所以會執行 onRejected 函示。
最後回傳 “onRejected function called: Oh no!”

1
2
3
4
5
6
7
8
9
Promise.resolve()
.then( () => {
throw 'Oh no!';
})
.then( () => {
console.log( 'Not called.' );
}, reason => {
console.error( 'onRejected function called: ', reason );
});

reject() 的情況

1
2
3
Promise.reject()
.then( () => 99, () => 42 ) // onRejected returns 42 which is wrapped in a resolving Promise
.then( solution => console.log( 'Resolved with ' + solution ) ); // Resolved with 42

reject 的時候,會執行 onRejected 函示,也就是會回傳 42。

實務上的用法

實務上,推薦盡量使用 catch 去處理 promise reject 的情況,不建議使用兩個參數 then() 語法。

1
2
3
4
5
6
7
8
9
10
11
Promise.resolve()
.then( () => {
// Makes .then() return a rejected promise
throw 'Oh no!';
})
.catch( reason => {
console.error( 'onRejected function called: ', reason );
})
.then( () => {
console.log( "I am always called even if the prior then's promise rejects" );
});

參考資料

Promise then - MDN