J 筆記 - Async/Await in Array

Async/Await in Array Loops

隨著 ES6Promise 解決 callbacks hell 的問題後,取得代之的就是 Es7Async/Await,都是為了達成 AJAX 獲得更好的體驗。這篇簡單紀錄一下使用 Async/Await 在迴圈裡面遇到的問題,以及該如何解決,以下就以三個例子來舉例:

ForEach

如果是 ForEach 搭配 Async/Await 的時候,會發生什麼事情呢?我們直接來深入了解吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const urls = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'
];

async function getTodos() {
await urls.forEach(async (url, idx) => {
console.log(`Received Todo ${idx+1}:`, await fetch(url));
});

console.log('Finished!');
}

getTodos();

雖然程式碼都沒有任何錯誤,也不會噴錯跑不成功,但是相信眼尖的你一定會注意 Finished! 居然在 await urls.forEach 之前就執行了!不是說好的 asyncawait 嗎?誰跟你說好~如以下的結果所示:

1
2
3
4
5
6
// Promise {<resolved>: undefined}

Finished!
Received Todo 1, Response: { type: "cors", url: "https://jsonplaceholder.typicode.com/todos/1", redirected: false, status: 200, ok: true, … }
Received Todo 3, Response: { type: "cors", url: "https://jsonplaceholder.typicode.com/todos/3", redirected: false, status: 200, ok: true, … }
Received Todo 2, Response: { type: "cors", url: "https://jsonplaceholder.typicode.com/todos/2", redirected: false, status: 200, ok: true, … }

那不能用 ForEach 我們該如何解決呢?來看看底下的兩種方式吧!

Map + Promise.all

Promise.all()

Promise.all() 方法回傳一個 Promise 物件,當引數 iterable 中所有的 promises 都被實現(resolved),或引數 iterable 不含任何 promise 時,被實現。或以第一個被拒絕的 promise 的原因被拒絕。

  • 一個已被實現(already resolved)的 Promise,若傳入的 iterable 為空。
  • 一個非同步地被實現(asynchronously resolved)的 Promise 若傳入的 iterable 不含 promise。注意,Google Chrome 58 對此情形回傳一個已被解決的 promise。
  • 一個擱置(pending)的 Promise,對所有剩餘情形。此 promise 接著被非同步地被 resolved/rejected(只要堆疊為空)當 iterable 中所有的 promises 都被實現,或其中一個被拒絕。參見下方關於”Promise.all 的非同步與同步性質”的例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const urls = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'
];

async function getTodos() {
const promises = urls.map(async (url, idx) =>
console.log(`Received Todo ${idx+1}:`, await fetch(url))
);

await Promise.all(promises);

console.log('Finished!');
}

getTodos();

For…of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const urls = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'
];

async function getTodos() {
for (const [idx, url] of urls.entries()) {
console.log(`Received Todo ${idx+1}:`, await fetch(url));
}

console.log('Finished!');
}

getTodos();

上面兩種方法都可以得到:

1
2
3
4
Received Todo 1, Response: { type: "cors", url: "https://jsonplaceholder.typicode.com/todos/1", redirected: false, status: 200, ok: true, … }
Received Todo 2, Response: { type: "cors", url: "https://jsonplaceholder.typicode.com/todos/2", redirected: false, status: 200, ok: true, … }
Received Todo 3, Response: { type: "cors", url: "https://jsonplaceholder.typicode.com/todos/3", redirected: false, status: 200, ok: true, … }
Finished!

參考資料