# [JavaScript] `callback`、`Promise` 及 `async` / `await`
###### tags: `前端筆記` `非同步處理`
## 建立 `Promise`
`Promise` 必須由 `new` 建構,且需傳入一個 callback,該 callback 會立即地同步被呼叫。傳入的 callback 又接受兩個 callbacks,作為該 `Promise` 的解析能力(監聽 `Promise` 的狀態,並依照狀態呼叫對應的 callback),與常見的 error first 風格不同,第一位為成功處理(習慣上稱為 `resolve`),第二位為錯誤處理(習慣上稱為 `reject`)。
```javascript=
// ref. [MDN - Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise)
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
promise1.then((value) => {
console.log(value);
// expected output: "foo"
});
console.log(promise1);
// expected output: [object Promise]
```
## `Promise` 的三種狀態
Promise 有分三種狀態且必然會在其中一個狀態之中:pending(等待中)、fulfilled(已實現)或 rejected(已拒絕)。
==由 pending 轉為 fulfilled / rejected==

[*ref. Promise*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#incumbent_settings_object_tracking)
## Promise 確保的是未來值
以書中(你所不知道的 JS 非同步處理與效能)的買漢堡範例(P.50 - 51):
跑去櫃檯點漢堡(發非同步請求)-> 店員給我取餐號碼 -> 思考如果有漢堡我就找朋友來吃漢堡(then 的第一位 parametera)-> 如果沒漢堡就退錢(catch)
```javascript=
const example = () => {
return new Promise((resolve, reject) => {
買完漢堡取得的取餐編號
if (漢堡有漢堡) {
resolve(漢堡)
} else if (沒漢堡) {
reject(沒漢堡)
}
})
};
example().then((漢堡) => {
找朋友來吃(漢堡)
}).catch((沒漢堡) => {
console.log(因為沒漢堡)
退錢()
})
```
因為等待餐點的途中不知道最後有沒有漢堡,但 Promise 確保的未來值(以這個範例是:有漢堡 / 無漢堡)可以讓開發者事先(也就是等到資料來之前)藉由未來值安排任務。
### 以 callback 粗略地模仿 callback 確保未來值的概念
```javascript=
const fetchA = (callback) => {
setTimeout(() => {
return callback(2);
}, 5000) // 模擬打 API 等待的時間
};
const fetchB = (callback) => {
return callback(3);
};
const add = (getA, getB, callback) => {
let a;
let b;
getA((aValue) => {
a = aValue;
// 確保 b 也有值
b && callback(a + b);
})
getB((bValue) => {
b = bValue;
// 確保 a 也有值
a && callback(a + b)
})
};
add(fetchA, fetchB, (sum) => {
console.log(sum); // 5
});
console.log('out of the add function');
// console.log 的順序
// 'out of the add function'
// 5
```
即便一開始會先結束 `add` 的 execution context,但是因為傳入的 `fetchA` 有設定計數器,待計數器結束 + call stack 空的話就會回去執行 `fetchA` 的 execution context。
### `Promise` 改寫同樣的例子
```javascript=
const add = (...promises) => {
return Promise.all(promises).then((values) => values[0] + values[1]);
};
const fetchA = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 5000) // 模擬打 API 的時間
});
};
const fetchB = () => {
return new Promise((resolve) => {
resolve(3);
});
};
add(fetchA(), fetchB()).then((sum) => console.log(sum));
console.log('out of promises');
// console.log 的順序為
// 'out of promises'
// 5
// -> 因為 Promise 也確保「非同步執行」
```
`Promise` 確保未來值,所以不需要先像純 callback 的例子先手寫確保值進來的判斷。反之,可以直接寫接到值之後的任務為何(也就是執行兩數相加的任務)。
## `Promise` 的錯誤處理
### 簡略說明 `Promise` 對待任務成功與失敗的流程
`Promise` 不會確認任務的細節,只會在任務成功完成或者失敗時告訴我們,我們只需要專注處理「任務成功(completion)完成後或者失敗(error)後」該做什麼即可。以假的程式碼可以思考成這樣子:
```javascript=
// 程式碼參考自《你所不知道的 JS 非同步處理與效能 - P. 57》
const foo = () => {
// 回傳 Promise 進行非同步處理的任務
// Promise 製作一種 `listener` 的時間通知的能力回傳
}
foo().on('completion', () => {
// 任務成功,可以進行下一步了
})
foo().on('error', () => {
// 任務失敗...
})
```
### 來試著用真實的程式碼吧
```javascript=
const fetchApi = () => {
return new Promise((resolve, reject) => {
reject('error');
});
};
fetchApi()
.then((value) => {
// 永遠不會執行到這裡
console.log('success');
console.log(value);
}, (error) => {
// Promise 失敗了,執行這裡的錯誤處理
console.log(error);
});
// 'error'
```
`then` 可以放兩個 callbacks,第一位在該 `Promise` 任務成功時會被叫用,第二位為選填,如果再該 `Promise` 任務失敗時會叫用:`then(successHandler, errorHandler)`
由上面的例子得知,我們需要使用 `then(successHandler, errorHandler)` 串接 `Promise`,並且依照 `Promise` 的狀態,將任務寫在對應的狀態之內。
### `then()` 的串接執行流程
- 每次在一個 `Promise` 呼叫 `then(...)` 時,`then` 就會創建一個新的 `Promise` 供我們繼續串接
- 如果 `then()` 沒有回傳值,就會自動回傳 `undefined`,如果有傳值,那麼接下來的 `then` 也可以取得該值
```javascript=
// Promise 已拒絕
const successHandler = (string) => {
console.log(string);
console.log('in success handler');
};
const errorHandler = (error) => {
console.log(error);
console.log('in error handler');
};
console.log(Promise.reject('no').then(successHandler, errorHandler));
// Promise {<pending>}
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: undefined
// no
// 如果有傳值
const successHandler = (string) => {
console.log(string);
console.log('in success handler');
};
const errorHandler = (error) => {
console.log(error);
console.log('in error handler');
return { x: 123 }
}
console.log(Promise.reject('no').then(successHandler, errorHandler));
// Promise {<pending>}
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: Object -> {x : 123}
// no
```
```javascript=
// 《你所不知道的 JS 非同步處理與效能》 P.74 - 75
var p = Promise.resolve(21)
var p2 = p.then((v) => {
console.log(v); // 21
return v * 2;
});
p2.then((v) => {
console.log(v * 2);
})
// 但是其實不用這麼麻煩,因為 then 回傳 fulfilled Promise 的緣故,我們可以直接串接
var p = Promise.resolve(21);
p.then((v) => {
console.log(v); // 21
return v * 2; // 42
}).then((v) => {
console.log(v); // 42
});
```
### 如果 `Promise` 串連的某一步出錯怎麼辦?下一鏈接上一鏈的錯誤
```javascript=
// 假設 request 是一個使用 Promise 打 API 的第三方工具
request('apiUrl1')
// Step 1
.then((response) => {
foo.bar(); // undefined, error
// 永遠不會到這裡
return request('apiUrl2')
}, (error) => {
// 也永遠不會到這裡
console.log(error);
})
```
#### 為什麼不會執行第 10 行的錯誤處理?
因為 5 至 13 行的 callbacks 是監聽上一層 `Promise` 的狀態的結果觸發不同的 callback(如果成功便執行 5 - 10 行,失敗便執行 10 至 13 行)。
當 `Promise` 的狀態變動後便無法被更改,所以 fulfilled 執行的 callback 內出錯是沒辦法由處理 rejected 的 callback 捕捉,必須由下一鏈接上一鏈(也就是 5 - 13 行的 `then`)捕捉錯誤。
```javascript=
// 假設 request 是一個使用 Promise 打 API 的第三方工具
request('apiUrl1')
// Step 1
.then((response) => {
foo.bar(); // undefined, error
// 永遠不會到這裡
return request('apiUrl2')
}, (error) => {
// 也永遠不會到這裡
console.log(error);
}).then(() => {
// 也永遠不會到這裡
console.log('in second success handler');
}, (error) => {
// Step 2
// 捕捉到上一層的錯誤
console.log(error); // ReferenceError: foo is not defined
console.log('in second error handler'); // in second error chain
return 'Hello World';
}).then((string) => {
// Step 3
// 因為上一鏈有回傳值,使得 Promise chain 又變回 fulfillment 的狀態,我們就可以繼續串接
console.log(string); // Hello World
});
```
## `catch` 有錯誤就直接丟進來
在實際的使用上,常用 `catch()` 來處理 `Promise` 的錯誤,而不是使用 `then()` 第二位 callback 處理錯誤,因為 `catch()` 可以直接處理每一鏈的錯誤,就不用以下一鏈處理上一鏈的方式處理。
```javascript=
Promise.resolve(123)
.then((number) => {
// Step 1
foo.bar(number); // undefined, error!
return number;
})
.catch((error) => {
// Step 2
// catch 處理錯誤
console.log(error);
// 回傳值,讓 promise 回覆成 fulfillment 的狀態
return 123;
})
.then((number) => {
// Step 3
// 繼續串接 ...
console.log(number);
});
// ReferenceError: foo is not defined
// 123
```
不同於 `then()` 接受兩個 callbacks(第一位負責 fulfillment,第二位負責 rejection),`catch()` 只接受一個 rejection callback,並自動替補預設的 fulfillment callback,所以等同於:
```javascript=
// 《你所不知道的 JS 非同步處理與效能》P. 102
then(null, ...);
p.then(fulfilled);
p.then(fulfilled, rejected);
p.then(null, rejected);
p.catch(rejected);
```
### 但如果 `catch` 裡面又有錯誤怎麼辦?
```javascript=
Promise.resolve(123)
.then((number) => {
// Step 1
foo.bar(number); // undefined, error!
return number;
})
.catch((error) => {
// Step 2
// catch 處理錯誤
console.log(error);
foo.bar(123) // undefined, error!
// 永遠不會執行
return 123;
})
.then((number) => {
// 永遠接不到了
console.log(number);
})
.catch((secondError) => {
console.log(secondError);
// 如果 catch 本身有錯誤就必須回到下一鏈處理上一鏈的方式 ...
})
// ReferenceError: foo is not defined
// ReferenceError: foo is not defined
```
目前還是沒有一個真的 100% 確保無錯誤的處理,因為一直瘋狂寫 `catch` 還是沒辦法避免 `catch` 內部出錯的可能...
### `try...catch...` 無法捕捉非同步處理的錯誤
==`try...catch...` 只能用來捕捉同步的錯誤,在非同步處理上無法捕捉。==
```javascript=
// 《你所不知道的 JS 非同步處理與效能》 P.85
function foo () {
setTimeout(() => {
bar.foo();
}, 100);
}
try {
foo();
// 直接拋出錯誤
} catch (error) {
// 永遠到不了這裡
}
```
同理,也沒辦法用在 `Promise`:
```javascript=
Promise.reject(123)
.then((number) => {
try {
console.log(number);
return number;
}
catch(error) {
console.log(error);
}
});
// Uncaught (in promise) 123
```
## 從傳入 callbacks 再看結果叫用,變成先等未來值後再傳 callbacks
### 1. 不需要在叫用函式時把 callback 當作參數輸入
以前的方式在叫用函式時必須把 callback 當作參數傳入函式叫用,不過當程式碼複雜度高時會造成閱讀上的困難。
```javascript=
// 需要把一大串 callback 當作參數傳入函式叫用
func1(30, (error, data) => {
if (data) {
console.log(data);
// 正確
func1(20, (error, data) => {
if (data) {
console.log('two');
} else {
console.log('nonono');
}
})
} else {
console.log(error);
// 錯誤
}
});
```
### 2. 使用 `Promise` 不需要在「叫用時」輸入一大串 callback,而是在回傳的 Promise 中使用 `.then()`及 `.catch()`這兩個方法放入不同時機時要執行什麼樣子的任務(callback)
- `.then()`負責執行當條件滿足時後會做 ...
- `.catch()`負責執行當條件不滿足(被拒絕、發生錯誤時)後會做...
```javascript=
// 以 fetch 來看
fetch('url')
// 如果回應 OK 的話
.then((response) => {
// 解析後會回傳一個已經實現的 Promise
return response.json(); })
// 所以透過 .then 讀取值 + 新增接下來的任務
.then((data) => {
// 我要的資料...
})
// 如果回應有問題的話
.catch((error) => {
// handle error
})
// 只需要在成功(.then)及失敗(.catch)中放入對應的 callback,不需像以前一樣需要把 callback 當作叫用函式的參數傳入
```
### 3. 如果有多個任務需要執行的話只要注意有回傳 `Promise` 供下一組 `.then()` 及 `.catch()` 串接就可以繼續執行了
```javascript=
function promise (num) {
return new Promise((resolve, reject) => {
if (num > 3) {
resolve();
} else {
reject();
}
});
}
promise(10).then(() => {
console.log('NICE!');
return promise(20)
}).then(() => {
console.log('NICE!');
}).catch((error) => console.log('nono'));
```
## Promise 模式
### 1. 等到全部的資料都完成再一次給我的 `Promise.all([])`
不在乎誰先誰後,我想要一次獲得全部的結果
==適合用在等到全部 API 都回傳資料後再做其他事情==
- 陣列的索引值 !== 執行順序
- 如果輸入的不是 Promise 物件,會被 `Promise.resolve()` 轉換為已經實現(fulfilled)的 Promise
- 只要其中的一個 Promise 被拒絕(reject)就會立即終止其他 Promise 的執行,並回傳該被拒絕的 Promise 物件。==全部的 Promise 結果都會被丟棄==
- 以陣列形式回傳資料(如果每個 `Promise` 都成功的話)
等到最後都沒問題,可用 `.then(callback)` 取得以陣列形式包覆的值
```javascript=
// Promise.resolve() 會直接建立一個已經實現的 promise
const promise1 = Promise.resolve('My name is Lun.');
// 會被 Promise.resolve() 自動轉為一個已經實現的 promise
const promise2 = 'Hello';
const promise3 = new Promise((resolve, reject) => { setTimeout(resolve('third promise'), 1000)});
// 錯誤就會馬上停止其他 Promise 並回傳被拒絕的 Promise
// const promise4 = Promise.reject('No!');
Promise.all([promise1, promise2, promise3]).then((value) => console.log(value));
// ['My name is Lun.', 'Hello', 'third promise']
```
### 2. `Promise.race([])`
只要其中一個好就好,其他的慢慢來就好,所以只會得到最先完成的結果,但是剩餘的 Promise 仍會繼續執行
```javascript=
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p1'), 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p2'), 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p3'), 500)
})
Promise.race([p1, p2, p3])
.then(value => {
console.log(value)
})
.catch(err => {
console.log(err.message)
})
// p3
```
#### 使用 `Promise.race()` 處理逾時等候的問題
```javascript=
const timeoutPromise = (delayTime) => {
return new Promise((_, reject) => {
setTimeout(() => {
reject(`超過時間限制了:${delayTime / 1000} 秒`);
}, delayTime);
});
};
const fetchDataP = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('data');
}, 5000);
});
};
Promise.race([
fetchDataP(),
timeoutPromise(2000)
])
.then((data) => {
console.log(data)
})
.catch((error) => {
// 會執行錯誤處理
console.log(error);
});
// 超過時間限制了:2 秒
```
### 3. `Promise.allSettled([])` 等到全部都由結果再給我,但是不會管成功與否
雖然和 `Promise.all([])` 很像,要等到全部的 `Promise` 由結果才會執行主要的 `Promise`(也就是 `Promise.allSettled()`),但與 `Promise.all([])` 不同的地方是,如果傳入的 `Promise` 有一個失敗的話並不會直接捨棄其餘 `Promise` 且終止主要 `Promise` 的解析(直接進入錯誤),而是乖乖等到其他 `Promise` 都解析完,並自動將主要 `Promise` 變為 fulfillment 的狀態。
```javascript=
// ref. [MDN - Promise.allSettled()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises)
.then((results) => results.forEach((result) => console.log(result.status)))
// 即便傳入的 Promise 為 rejected 也不會到這裡,因為 allSettled 會自動將主要 Promise 視為 fulfillment 的狀態
.catch((error) => console.log(error))
// expected output:
// "fulfilled"
// "rejected"
```
## `Promise` 的限制
### 1. 單一解析
`Promise` 只要變換狀態後就不可以再更動狀態了(不可變的 immutable),意即一個 `Promise` 被解析(狀態更動: fulfilled / rejected)就沒辦法再重複解析:
```javascript=
// 《你所不知道的 JS 非同步處理與效能》P. 108 - 109
// 點擊 #mybtn 觸發 request 的 Promise
var p = new Promise((resolve, reject) => {
click("#mybtn", resolve)''
});
p.then((event) => {
var btnID = event.currentTarget.id;
return request('url' + btnID);
}).then((text) => {
console.log(text);
})
```
以上例子只會再第一次的點擊時有效,之後就會無視,因為 `p` 的 `Promise` 已經被解析了。
```javascript=
// 《你所不知道的 JS 非同步處理與效能》P. 109
// 必須每次點擊時都重新建立 Promise 的串連
click('#mybtn', (event) => {
var btnID = event.currentTarget.id;
request('url' + btnID).then((text) => console.log(text));
})
```
### 2. 發出的承諾便無法取消
```javascript=
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(42);
}, 5000);
});
const timeoutPromise = (delayTime) => {
return new Promise((_, reject) => {
setTimeout(() => {
reject('超過時間' + ' ' + delayTime / 1000 + '秒');
}, delayTime);
})
};
Promise.race([
p,
timeoutPromise(2000);
]).then((data) => {
console.log(data);
}).catch((error) => {
// 執行錯誤
console.log(error); // 超過時間 2 秒
});
p.then((data) => console.log(data));
// 等待至少 5 秒後...
// 42
```
需要手寫閘門,控制是否執行:
```javascript=
let isOk = true;
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(42);
}, 5000);
});
const timeoutPromise = (delayTime) => {
return new Promise((_, reject) => {
setTimeout(() => {
reject('超過時間' + ' ' + delayTime / 1000 + '秒');
}, delayTime);
})
};
Promise.race([
p,
timeoutPromise(2000)
]).then((data) => {
console.log(data);
}).catch((error) => {
// 執行錯誤
console.log(error); // 超過時間 2 秒
// 關閉閘門
isOk = false;
});
p.then((data) => {
// 手動監測閘門是否開啟!
if (isOk) {
console.log(data);
}
});
```
## `Promise` 回顧
1. `Promise` 確保未來值,並監聽 `Promise` 的狀態,我們只需要依照狀態將任務(callback)事先排程,`Promise` 會依照狀態結果執行對應的排程任務
2. `Promise` 的效能會比傳統純 callbacks 還慢,但可以確保程式碼閱讀性不會像純 callbacks 一樣難懂
3. 有等到全部 Ok 再來,一個不 Ok 就不行的 `Promise.all([])`,以及看誰先,我只要先的 `Promise.race([])`
## ES7 推出的語法糖 `async` / `await` 讓非同步執行的程式碼排列看起來就像是同步執行
多虧 `async` / `await` 的幫助,讓原本寫在 `Promise.then()` 內的 callback 可以跳出 `Promise` 的狀態監聽,並寫在 `async function` 內,讓程式碼看起來就像是同步執行一般好讀。==(把任務寫在確保未來值的 callback 內 -> 把未來值從確保未來值的 callback 解析出來)==
```javascript=
async function getData () {
// 解析未來值
const resolveValue = await <Promise>
// 任務 ...
console.log(resolveValue);
}
// ----- 使用 Promise -----
const getData = () => {
return new Promise((resolve) => {
resolve(data);
});
};
getData().then((data) => {
// 任務 ...
console.log(data);
})
```
### keyword: `async`
`async` 放在函式前面,JavaScript 會把該函式視為 `async function`。
```javascript=
// function declaration
async function getData () {
...
}
// function expression
const getData = async() => {
...
}
```
### `async function` 回傳的是一個 `Promise`
`async function` **「總是」** 會回傳一個 `Promise`。
如沒有 `Promise` 的錯誤的話,回傳的 `Promise` 之狀態都會為 *resolved*。如果回傳的值不是 `Promise`,則會自動轉為 `Promise`(狀態為 *resolved*)其值為當初想要回傳的值。
> A Promise which will be resolved with the value returned by the async function, or rejected with an exception thrown from, or uncaught within, the async function.
> [MDN - async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
```javascript=
async function f() {
return 123;
}
// 以上等價於
async function f() {
return Promise.resolve(123);
}
console.log(f());
// Promise {<fulfilled>: 123}
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: 123
// 因為回傳 Promise,所以也可以用 then 串連
f().then((number) => console.log(number)); // 123
// 因為 async 就會自動回傳 Promise,如果沒傳任何值,自動回傳的 Promise 就不帶值
const asyncFuncNoReturn = async() => {};
console.log(asyncFuncNoReturn());
// Promise {<fulfilled>: undefined}
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: undefined
asyncFuncNoReturn().then((value) => console.log(value)); // undefined
```
### `await`
```javascript=
async function getData () {
const result = await <Promise>
}
```
1. 只能在 `async function` 內使用
2. `await` 是等待 `Promise` 的,換言之,給 `await` 非 `Promise` 的物件是起不到「等我做完再繼續」的效果
3. 在 `async function` 內只要碰到 `await`,會等到與 `await` 連接的 `Promise` 被解析後(待這個 `Promise` 從 `pendding` -> `fulfilled` / `rejected`),`async function` 的 execution context 才會繼續往下執行。
- 可以想像成任務卡在 `await`,必須等到 `await` 解完才可以繼續往下解
4. 等待 `await` 的 `Promise` 解析完畢後,如果 `Promise` 為 `fulfilled`,便會回傳該 `Promise` 回傳的值。如果 `Promise` 為 `rejected` 便扔(throw)出錯誤
```javascript=
const getData = () => {
// Step 2
return new Promise((resolve) => {
// 模仿打 API 等待資料回傳
// Step 2 - 1
setTimeout(() => resolve('data'), 5000)
})
};
const asyncFunc2 = async() => {
const data = await getData(); // Step 1
console.log(data); // Step 3,待 Promise 解析之後得到 Promise 的未來值
}
asyncFunc2();
// data (至少等 5 秒後印出)
```
以第一個 `Promise` 的結果為第二個請求的依據:
```javascript=
const get = () => {
// Step 1 - 2
return new Promise((resolve) => {
// Step 1 - 3
// 等待 4 秒後進入 Step 2
setTimeout(() => resolve('hello'), 4000);
})
};
const process = (value) => {
// Step 3 - 2
return new Promise((resolve) => {
// Step 3 - 3
// 等待 2 秒後進入 Step 4
setTimeout(() => resolve(`${value} world!`), 2000)
})
};
const main = async() => {
const dataFromFirstPromise = await get(); // Step 1
console.log('end of the fist get'); // Step 2,等待 Step 1 的 Promise 完全解析完
const finalData = await process(dataFromFirstPromise); // Step 3
console.log(finalData); // Step 4
return finalData; // Step 5
};
// 因為 async function 會自動回傳 Promise,所以可以使用 Promise 取得未來值
main().then((finalData) => {
console.log(`The final data is: ${finalData}`);
});
// end of the fist get
// hello world!
// The final data is: hello world!
```
如果同樣的例子單純用 callback 做的話:
```javascript=
const get = (callback) => {
// Step 2 - 2
setTimeout(() => {
// Step 2 - 3
callback('hello');
}, 4000)
};
const process = (data, callback) => {
// Step 4
setTimeout(() => {
// Step 4 - 2
callback(`${data} world`)
}, 4000)
};
const main = () => {
// Step 2
// Step 3 叫用 get 內的 callback
get((firstData) => {
// Step 3 - 2
console.log(`End of the fist get, the first data is: ${firstData}`);
// Step 3 - 3,即將進入 process 的 execution context
// Step 4 - 3 叫用 process 內的 callback
// NOTE: 因為 function scope 的關係所以可以讀取到 firstData
process(firstData, (finalData) => {
// Step 4 - 4
console.log(`The final data is: ${finalData}`);
})
})
};
// Step 1
main();
// End of the fist get, the first data is: hello
// The final data is: hello world
```
僅使用 `Promise` 不借用 `async` / `await` 的神力:
```javascript=
const get = () => {
// Step 2
return new Promise((resolve) => {
setTimeout(() => {
// Step 2 - 2
resolve('hello');
// Step 2 - 3,至少等 4 秒後進入 process 的 execution context
console.log('End of the first get, the first data is: hello');
}, 4000)
})
};
const process = (firstData) => {
// Step 3 - 2
return new Promise((resolve) => {
setTimeout(() => {
// Step 3 - 3
resolve(`${firstData} world`);
}, 4000)
})
}
const main = () => {
// Step 1,進入 main 的 execution context
get()
// process 就會變成第一個 Promise 的 high order function
// Step 3,進入 process 的 execution context
.then(process)
// Step 4,得到 process 的未來值
.then((finalData) => console.log(finalData));
}
main();
```
### 基本的錯誤處理
如果 `await` 等待的 `Promise` 狀態為 `rejected`,那 `await` 便會仍出錯誤:
```javascript=
async function f() {
await Promise.reject(new Error("Whoops!"));
}
// 等價於
async function f() {
throw new Error("Whoops!");
}
```
但藉由 `async function` 的神力,也可以用同步函式時處理錯誤的 `try...catch`:
```javascript=
const testCatchError = async() => {
try {
await Promise.resolve('yes!');
await Promise.resolve('yes2!');
await Promise.reject('no!');
// 反正只要有錯誤就會自動進到 catch
} catch (error) {
console.log(error);
}
};
testCatchError();
```
如果不想要在 `async function` 內處理,也可以像 Promise chain 一樣在最後新增捕捉全鏈錯誤的 `catch`:
(因為 `async function` 就是會回傳一個 `Promise`)
```javascript=
const testCatchError = async() => {
await Promise.resolve('yes!');
await Promise.resolve('yes2!');
await Promise.reject('no!');
};
testCatchError().catch((error) => console.log(`I got ${error}`));
```
## 其他例子
### 在 `async / await` 的函式中 JS 會等待 `await` 的任務結束後才會繼續執行 `async` 區塊接下來的任務
```javascript=
const fetchFirstData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('this is the first data you need');
}, 2000);
});
};
const fetchSecondData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('this is the second data you need');
}, 5000);
});
};
const getData = async (promise1, promise2) => {
const data1 = await promise1();
console.log(data1);
const data2 = await promise2();
console.log(data2);
console.log('end of getData');
};
getData(fetchFirstData, fetchSecondData);
```
### `async / await` 搭配 `Promise.all([])`
```javascript=
const fetchFirstData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('this is the first data you need');
}, 2000);
});
};
const fetchSecondData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('this is the second data you need');
}, 5000);
});
};
const getAllDataTogether = async () => {
const [first, second] = await Promise.all([fetchFirstData(), fetchSecondData()]);
console.log(first);
console.log(second);
};
getAllDataTogether();
```
## 參考資料
1. [[筆記] 認識同步與非同步 — Callback + Promise + Async/Awai](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E5%BF%83%E5%BE%97-%E8%AA%8D%E8%AD%98%E5%90%8C%E6%AD%A5%E8%88%87%E9%9D%9E%E5%90%8C%E6%AD%A5-callback-promise-async-await-640ea491ea64)
2. [利用 async 及 await 讓非同步程式設計變得更容易](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Asynchronous/Async_await)
3. [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
4. [Promise.prototype.then()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
5. 你所不知道的 JS 非同步處理與效能 - 第三章 Promise
6. [你懂 JavaScript 嗎?#24 Promise](https://ithelp.ithome.com.tw/articles/10207017)
7. [JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.huli.tw/2019/10/04/javascript-async-sync-and-callback/)
8. [Async/await](https://javascript.info/async-await)
9. [[JS] Async and Await in JavaScript](https://pjchender.dev/javascript/js-async-await/)