非同步執行最迷人的一點就是我們可以讓他並行(Parallel), 加快我們程式的速度!例如我們要下載 3 本書的資訊,每讀取一本需要花費一秒鐘,讀取三本就需要三秒鐘。但,如果在頻寬允許的情況下,我們一次讀取三本,那就只需要花費一秒鐘。
今天要學習的就是使用 Promise
達到並行的目的!
首先,我們先準備一個假的 API 讓我們讀取書本的資訊:
// 檔名: getBooksDetails.ts
export type bookIndex = 'imposter_cure'|'atomic_habits'|'evil_girl';
const books = {
imposter_cure: {
name: '冒牌者症候群',
author: '潔薩米.希伯德',
price: 221,
publisher: '商周出版',
url: 'https://readmoo.com/book/210117591000101',
},
atomic_habits: {
name: '原子習慣',
author: '詹姆斯.克利爾',
price: 231,
publisher: '方智出版',
url: 'https://readmoo.com/book/210109063000101',
},
evil_girl: {
name: '末日前,我把惡魔少女誘拐回家了',
author: '黑貓C',
price: 231,
publisher: '奇幻基地',
url: 'https://readmoo.com/book/210117051000101',
}
}
export async function getBooksDetails(handle: bookIndex) {
return new Promise<{
name: string,
author: string,
price: number,
publisher: string,
url: string
}>(resolve => {
setTimeout(() => {
resolve(books[handle]);
}, 1500);
});
}
在這個假的 API 中,有一個關鍵自是我們之前沒有介紹的,那就是export
。關於 export
, 他的目的就是要讓其他的 ts
可以在 import 它之後可以存取到它。
也就是說,當其他人 import 這個 getBooksDetails.ts 後,他只能存取到 bookIndex
和 getBookDetails
, 由於 books
沒有 export
, 儘管他不是 private
, 其他 import getBooksDetails.ts 的人也無法存取到 books
。
首先,如果一筆一筆資料讀取的話,我們會使用一個 for
迴圈把所有想要的資料讀取完畢!但在那之前,我們必須要先把這個假的 API 給 Import 進來。
import {getBooksDetails} from './getBooksDetail';
import {bookIndex} from './getBooksDetail';
接著宣告一個陣列,裡面存放所有我們想要讀取的圖書資 Index:
const books: Array<bookIndex> = ['imposter_cure', 'atomic_habits', 'evil_girl'];
最後,我們就可以寫一個非同步的函式,來讀取所以要想的書籍資料:
async function readBooksDetails() {
for (const b of books) {
const item = await getBooksDetails(b);
console.log('-------------------------------------')
console.log(
'書名:' + item.name + '\n' +
'作者:' + item.author + '\n' +
'價格:' + item.price + '\n' +
'出版社:' + item.publisher + '\n' +
'網址:' + item.url);
console.log('-------------------------------------')
}
}
readBooksDetails();
不過,還記得嗎?我們假設每讀取一本書籍資訊需要花費時間一秒鐘,所以,這個迴圈跑完一共會需要三秒鐘。那如果我想要在一秒鐘就取得三本書的資料該怎麼做呢?
這時候我們就可以使用 Promise.all
達到並行, 同時讀三本書,假設每一本都是需要花一秒,所以,一口氣同時讀取三本,最後只需要花一秒鐘就可以完成了。
// 先透過 map function 取得三本書的 promise
const allPromises = books.map(getBooksDetails);
// 再使用 Promise.all 把所有的 promise 合併起來
const combinedPromise = Promise.all(allPromises);
// 最後執行它, 我們只會花一秒鐘就可以得到 details 了
combinedPromise.then(
values => {
console.log(values);
},
reason => {
console.log('Error:' + reason);
});
}
輸出的結果如下:
[ { name: '冒牌者症候群',
author: '潔薩米.希伯德',
price: 221,
publisher: '商周出版',
url: 'https://readmoo.com/book/210117591000101' },
{ name: '原子習慣',
author: '詹姆斯.克利爾',
price: 231,
publisher: '方智出版',
url: 'https://readmoo.com/book/210109063000101' },
{ name: '末日前,我把惡魔少女誘拐回家了',
author: '黑貓C',
price: 231,
publisher: '奇幻基地',
url: 'https://readmoo.com/book/210117051000101' } ]
在學習 Promise.all
的過程中,意外看到 Promise.race
, 雖然目前還沒想到使用他的時機點,但這個也是滿有趣的。所以就一並在這邊跟大家分享。
關於 Promise.race
它的功能就是,當我們去並行數個 promise
時,他只會回傳遞一個回來的 promise
的結果!也就是說,我們同樣以上面書本的例子來看,假設每一本書回覆的時間是不固定的,有時快有時慢,但都在一秒上下!這時候,如果我們使用 Promise.race
, 我們得到的結果就有可能是第一本書,也有可能是第二本書,也有可能是第三本書,就看哪一筆資料回覆的時間最快,結果就會是哪一筆。
他的操作方式與 Promise.all
差不多,就只是結果不一樣而已!
const resultOfPromiseThatWins = Promise.race(allPromises);
resultOfPromiseThatWins.then(
value => {
console.log('Promise with Race: ' + value.name)
},
reason => {
console.log('Reject:' + reason)
}
);
而他輸出的結果,書本的名稱就會是不一定,看誰快就是誰!
Promise with Race: 冒牌者症候群