過去以 Callback 撰寫非同步會容易在呼叫多層之後產生「Callback Hell」難以維護的問題,而 ES6 提供 Promise 來更方便的執行非同步。關於 JavaScript 執行方式、非同步及 Callback 原理解釋請參考前文:
在還沒有 Promise
的時候,我們為了控制多個函式間執行的順序,就會透過 Callback 的形式將 function 當作參數傳進去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var first = function (callback ) { setTimeout(function ( ) { console .log("This is first." ); callback(); }, 1000 ); }; var second=function ( ) { console .log("This is second." ); }; first(second);
無論 first()
在執行的時候要等多久, second()
都會等到 first()
執行完才會執行。也可以寫成下面這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function first (callback ) { setTimeout(() => { console .log("This is first." ); callback() }, 1000 ) } function second (callback ) { callback(() => { console .log("This is second." ); }) } second(first)
如果再增加 Callback 的數量:
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 function first (callback2 ) { setTimeout(() => { console .log("This is first." ); callback2() }, 1000 ) } function second (callback ) { setTimeout(() => { console .log("This is second." ); callback() }, 1000 ) } function third ( ) { console .log("This is third." ); } function main (callback ) { first(() => { second(() => { callback() }) }) } main(third)
當 Callback 太多層就會變成非常複雜的巢狀結構:
1 2 3 4 5 6 7 8 9 first(() => { second(() => { third(() => { fourth(() => { }) }) }) })
Promise Promise
提供的新語法讓非同步更加直觀,讓我們看一下它的建構函式:
1 new Promise ( function (resolve, reject ) { ... } )
下面是箭頭函式的寫法:
1 new Promise ( (resolve, reject ) => { ... } )
Promise 的狀態與流程 Promise 有三種 state(狀態)
Promise 運作流程圖:
Promise
物件中包含兩個參數: resolve
與 reject
, then
會接收 resolve
的 value, catch
則接收 reject
的 value。
讓我們看一個基本的 Promise 範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const p = new Promise (function (resolve, reject ) { setTimeout(function ( ) { let value = 1 console .log(value); resolve(value); }, 1000 ); }); p.then((value ) => { console .log('success:' + value) }) p.catch((error ) => { console .log('error:' , error) })
Pending 當 Promise
沒有 resolve
或是 reject
的時候,程式將會一直處在 pending
狀態:
1 2 3 4 5 6 7 var p = new Promise (function (resolve, reject ) {});p.then((value ) => { console .log('success' ); }) p.catch((error ) => { console .log('error:' , error); })
例如:送出一個請求一直沒有收到回應時, Promise 就會一直處於 pending 狀態。
Promise Chain 我們可以在 Promise
使用 .then
來進行串接,透過在 .then
裡面 return
一個值,讓這個回傳值以 Promise
物件的形式傳到下一個 .then
,形成 Promise Chain:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const p = new Promise (function (resolve, reject ) { setTimeout(function ( ) { let value = 1 ; resolve(value); }, 1000 ); }); p.then((value ) => { console .log(value); return value+1 ; }).then((value ) => { console .log(value); return value+1 ; }).then((value ) => { console .log(value); }).catch((error ) => { console .log('error:' , error) })
catch
如果是串在中間,在 new Promise
的時候是 reject
,會導致前面 .then
都不會被執行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const p = new Promise (function (resolve, reject ) { setTimeout(function ( ) { let value = 1 ; reject(value); }, 1000 ); }); p.then((value ) => { console .log('Start' ); return value+1 ; }).catch((error ) => { console .log('error:' , error) }).then((value ) => { console .log('End' ); })
Promise 使用範例 我們可以在 .then
中 return
一個 new Promise
。
範例一:
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 35 const p = new Promise ((resolve, reject ) => { setTimeout(() => { let value = 1 ; resolve(value) }, 1000 ) }) p.then((value ) => { console .log("This is the first value :" + value); return value + 1 }) .then((value ) => { console .log("This is the second value :" + value); return new Promise ((resolve, reject ) => { setTimeout(() => { resolve(value + 2 ) }, 1000 ) }) }) .then((value ) => { console .log("This is the third value :" + value); }) .catch((error ) => { console .log('error:' , error) })
範例二:
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 function p (value ) { return new Promise ((resolve, reject ) => { setTimeout(function ( ) { value++ resolve(value) }, 1000 ) }) } p(1 ) .then((value ) => { console .log("This is the first value :" + value); return p(value+1 ) }) .then((value ) => { console .log("This is the second value :" + value); return p(value+1 ) }) .then((value ) => { console .log("This is the third value :" + value); })
範例三:
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 function p (value ) { console .log("This value is :" + value); return new Promise ((resolve, reject ) => { setTimeout(function ( ) { value++ resolve(value) }, 1000 ) }) } p(1 ) .then(p) .then((value ) => { console .log(value) return value+2 }) .then(p) .catch((error ) => { console .log('error:' , error) })
範例四:
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 const p = new Promise (function (resolve, reject ) { setTimeout(function ( ) { let value = 1 ; resolve(value); }, 1000 ); }); function funA (value ) { console .log("The value of function A is :" + value); const data = funC(value) return data + 1 } function funB (value ) { console .log("The value of function B is :" + value); } function funC (value ) { return value + 2 } p.then(funA) .then(funB)
Promise 錯誤處理 通常我們會將 .catch
放在最後面做錯誤處裡,當其中一個 .then
發生錯誤時,就會跳到 .catch
,而 .catch
之後的 .then
皆不會執行。
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 function p (value ) { return new Promise ((resolve, reject ) => { if (value > 2 ){ reject('Unexpected condition' ) } setTimeout(function ( ) { value++ resolve(value) }, 1000 ) }) } p(1 ) .then((value => { console .log("This is the first value :" + value); return p(value) })) .then((value => { console .log("This is the second value :" + value); return p(value) })) .then((value => { console .log("This is the third value :" + value); return p(value) })) .catch((error ) => { console .log(error) })
Promise.all Promise.all
可以傳入一個以上的 Promise
並同時執行,等到所有的 Promise
都回應狀態後,才會進入 .then
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function p (value ) { return new Promise ((resolve, reject ) => { setTimeout(function ( ) { if (value < 1000 ){ reject('Unexpected condition' ) } resolve(value) }, value) }) } Promise .all([p(1000 ), p(2000 ), p(3000 )]).then((value )=> { console .log('This value is :' , value); }).catch( err => { console .log(err) });
如果其中一個 Promise
中有出現 reject
,則直接進入 .catch
階段,而不會收到其他 Promise resolve
的回傳值。
Promise.race Promise.race
一樣可以傳入多個 Promise
並同時執行,但只回傳 Promise
中最快完成的,並接著執行 .then
,而其他的 Promise
皆不會執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function p (value ) { return new Promise ((resolve, reject ) => { setTimeout(function ( ) { resolve(value) }, value) }) } Promise .race([p(1000 ), p(2000 ), p(3000 )]).then((value )=> { console .log('This value is :' , value); }).catch( err => { console .log(err) });
參考文獻
callback, promise, async/await 使用方式教學以及介紹 Part I - Jack Yu | 傑克
[JS] Promise 的使用 | PJCHENder 私房菜
Day23 Promise 詳解(1/2)
Day24 Promise 詳解(2/2)
JavaScript - Promise (2)
JS 原力覺醒 Day14 - 一生懸命的約定:Promise