前言
那麼這一篇會來談談什麼是 Promise 以及 Async/Await 語法。
Promise 概念
Promise 其實就是承諾的意思,當然不是指 Promise 中文翻譯是承諾這件事,如果有這麼簡單我也不用特別寫成一篇文章。
那麼先來講講為什麼你要懂 Promise 這件事情。
你在開發時應該很常遇到所謂的「非同步」事件,例如以下程式碼預期應該是要依照順序跑:
1 2 3 4 5 6 7
| console.log('Start');
setTimeout(() => { console.log('My Name is Ray'); }, 0);
console.log('End');
|
但實際上結果卻是…
1 2 3
| 1. Start 2. End 3. My Name is Ray
|
那這件事情與 Promise 有什麼關聯性呢?實際開發來講,我們往往會預期希望結果是依照我們要的順序去運作,畢竟程式碼亂跳的話結果也一定會不同。
那 Promise 概念是什麼呢?雖然中文翻譯是一個承諾的意思,但實際上是如何呢?我們試著用比較現實生活層面來舉例。
你今天去人超多的百貨公司買一點小吃,例如…繼光香香雞

當你點好了繼光香香雞後店員給你了一個取餐單或者是取餐叫號器

等你拿到那一張取餐單 or 叫號器之後,你就會等到它叫你去取餐。
當然這段時間你依然可以去做點其他事情,只是你等一下會回頭處理他的事情,只是你一定會是先去繼光香香雞點餐後再去取餐。
所以你可以理解到一件事情 Promise 簡單來講就是我答應你我等一下一定會做什麼事情。
那 Promise 該如何撰寫呢?讓我們繼續往下看。
Promise
Promise 本身是一個建構函式,因此會需要使用到 new 語法來實例化它,而它主要會帶入兩個參數,分別是
1
| new Promise(function (resolve, reject) {});
|
而宣告方式有兩種
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
function myName () { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
|
而這兩者寫法並沒有太大差異,所以這邊主要看你自己需求來調整撰寫。
Promise 使用
而 Promise 的使用方式也相當簡單,當你宣告了一個 Promise 後,你可以使用 then 與 catch 串接 Promise 回傳的結果
1 2 3 4 5 6 7 8 9 10 11
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
myName() .then((res) => console.log('成功:'+ res)) .catch((error) => console.log('失敗:' + error));
|
那麼問題來了,什麼時候會跑 then 什麼時候會跑 catch 呢?其實這是我們可以決定的,前面我們有講到實例化 Promise 時會帶入兩個參數,分別是 resolve 與 reject,而當若結果回傳的是 resolve 則會跑 then,反之若是 reject 就是 catch。
Promise 靜態方法
除此之外 Promise 也有提供一些靜態方法(不需要實例化即可使用的方法):
Promise.all(iterable)
Promise.race(iterable)
Promise.reject(resason)
Promise.resolve(value)
Promise.all
當你有多個 Promise 事件需要一起執行與完成時,就可以使用 Promise.all
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const myName1 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray1'); }, 300); }) }
const myName2 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray2'); }, 300); }) }
Promise.all([myName1(), myName2()]) .then((res) => { console.log(res); })
|
這邊需要注意回傳的結果會與你一開始傳入的順序是相同的,而這個方法很適合用於打多隻 API 時。
Promise.race
Promise.race 簡單來講就是,當然當有任一個事件完成時,就會只回傳那一個事件,舉例來講 myName2 將會比 myName1 更快結束:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const myName1 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray1'); }, 1000); }) }
const myName2 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray2'); }, 100); }) }
Promise.race([myName1(), myName2()]) .then((res) => { console.log(res); })
|
Promise.reject & Promise.resolve
最後是關於 Promise.reject 與 Promise.resolve 的部分,這兩個方法就是只取得成功或是失敗得結果而已
1 2 3 4 5 6 7 8 9
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
Promise.resolve(myName()).then(res => console.log(res))
|
反之若改成 Promise.reject 則必定回傳失敗結果。
其他方法
最後其實還有兩個方法
Promise.any
Promise.allSettled
Promise.any 只要有任何一個成功,就必定會執行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const myName1 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray1'); }, 300); }) }
const myName2 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { reject('Ray2'); }, 300); }) } Promise.any([myName1(), myName2()]).then(res => console.log(res))
|
最後 Promise.allSettled 只要傳入的 Promise 事件都完成或者失敗後就會回傳結果與狀態
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const myName1 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray1'); }, 300); }) }
const myName2 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { reject('Ray2'); }, 300); }) } Promise.allSettled([myName1(), myName2()]).then(res => console.log(res))
|
Promise.allSettled 很適合用於確定所有 Promise 的執行狀態。
Promise 狀態
那麼 Promise 本身也有三種狀態
- pending(擱置)
- fulfilled(完成、實現)
- rejected(拒絕)
如果你有針對前面的程式碼稍微嘗試運行過的話,基本上你都會看到上面任一種的提示訊息而這也是 Promise 的狀態。
Promise.prototype.then 特別之處
其實 Promise.prototype.then 是一個很特別的方法,為什麼這樣說呢?在前面範例中,我們知道 then 代表著 Promise 成功,而 catch 則是失敗,因此一個 Promise 就會寫成這樣:
1 2 3 4 5 6 7 8 9 10 11
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
myName() .then((res) => console.log('成功:'+ res)) .catch((error) => console.log('失敗:' + error));
|
但是 Promise.prototype.then 是可以寫成這樣的:
1 2 3 4 5 6 7 8 9 10
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
myName() .then((res) => console.log('成功:'+ res), (error) => console.log('失敗:' + error))
|
而這兩者都是屬於被允許的範圍內,但通常來講我們還是會採用前者的方式 then & catch,畢竟錯誤訊息與成功訊息都放在同一個 then 內是格外的難閱讀,更不用說當邏輯越來越複雜時。
另外 then 是可以串接的,因此如果希望依照順序執行的話,除了使用 Promise.all 之外也可以透過 return 持續將結果往下一個 then 傳遞:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const myName1 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray1'); }, 300); }) }
const myName2 = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray2'); }, 300); }) } myName1().then((res) => { console.log(res); return myName2() }).then((res) => { console.log(res); })
|
ES7 Async/Await
那麼前面的 Promise 語法幫助了我們遠離早期開發的 Callback Hell 問題

讓我們可以從 Callback Hell 轉換為串聯的方式,但是實際開發來講,還是有可能會讓 Callback Hell 問題發生,因此 ES7 出現了一個新的語法,也就是 Async/Await,可以刊稱真正的 Callback Hell 救贖之光。
為什麼這樣說呢?我們都知道一個 Promise 都必須搭配 then 串接成功結果:
1 2 3 4 5 6 7 8 9 10 11
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
myName() .then((res) => console.log('成功:'+ res)) .catch((error) => console.log('失敗:' + error));
|
而 ES7 Async/Await 出現之後我們可以大幅的減少巢狀結構,只需要這樣寫即可
1 2 3 4 5 6 7 8 9 10
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
const res = await myName(); console.log(res);
|
早期瀏覽器是不能直接如上方這樣寫的,因為 await 語法必須包在一個函式內:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const myName = function() { return new Promise(function(resolve, reject) { setTimeout(() => { resolve('Ray'); }, 300); }) }
async function fn() { const res = await myName(); console.log(res); } fn();
|
是後來瀏覽器才允許你可以直接在頂層使用 await。
Async
那麼 async 語法有什麼特別的呢?簡單來講 async function 可以將原本的函式轉換成 Promise:
1 2 3 4 5 6 7
| async function fn() { return setTimeout(() => { console.log('Ray'); }, 2000); }
console.log(fn());
|

這邊要注意一件事情,async 等同使用 Promise.resolve:
1 2 3
| async function fn() { return 'Ray' }
|
其實是被隱含轉換成了以下:
1 2 3
| function fn() { return Promise.resolve('Ray'); }
|
Await
那麼 await 呢?其實 await 概念等同 then:
1 2 3
| async function fn() { return await 'Ray' }
|
底層轉換成了 then 的概念:
1 2 3
| function fn() { return Promise.resolve('Ray').then(() => undefined) }
|
async/await 特性
那麼看完前面的一些介紹之後,其實你就可以知道 async/await 可以更是大幅的優化 Promise 寫法,因此原本的 Promise 寫法可以如下:
1 2 3 4 5 6
| const myName = function() { return new Promise(function (resolve, reject) { resolve('Ray'); }) } myName().then((res) => console.log(res))
|
但是透過 async/await 特性之後,你可以改寫成以下:
1 2 3 4 5
| const myName = async () => 'Ray'; myName().then((res) => console.log(res));
myName().then(console.log);
|
甚至是這種極致的寫法:
1 2
| const myName = async () => 'Ray'; console.log(await myName());
|
try/catch
那麼最後是關於 async/await 常見的狀況,因為使用 async/await 語法後我們就無法取得錯誤訊息,因此若要捕抓錯誤訊息的話,就必須使用 try/catch,而使用的方式非常簡單
1 2 3 4 5 6
| const myName = async () => 'Ray'; try { console.log(await myName()); } catch(e) { console.log(e); }
|
透過這樣的方式,就可以補抓一些錯誤訊息了。
實戰相關
那麼在實戰上我們很常使用 axios or fetch 等 API 獲取遠端的資料,而這兩者也都是使用 Promise 製作的,因此你不但可以使用 then 串鏈之外,也可以使用 async/await 撰寫,這邊就不再多次說明了,就當作留給閱讀的人一個功課,可以試著去回顧自己之前寫的 axios 與 fetch 語法讓它更簡短更優化吧。
參考文獻
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement