# Promise ```typescript const promise = () => { let time = Math.round(300 + 500 * Math.random()) return new Promise((resolve, reject) => { console.log(`run ${time}ms`) if (Math.random() > 0.5) { setTimeout(resolve, time, `promise resolve in ${time}ms`) } else { setTimeout(reject, time, `promise reject in ${time}ms`) } }) } ``` ```typescript // 成功機率是 0.5 * 0.5 * 0.5 = 0.125 const PromiseAll = async () => { try { const result = await Promise.all([promise(), promise(), promise()]) // 每個 promise 都會執行,但 promise 的結果必須要全部成功,否則 reject ,可以確保所有的 promise 都是執行成功的 console.log(result) } catch (e) { console.log(e) } } ``` ```typescript // 成功機率 0.5% const PromiseRace = async () => { try { const result = await Promise.race([promise(), promise(), promise()]) // 每個 promise 都會執行,race 只關心第一個 resolve 或是 reject 的 promise , // 應用情景會是當我其中一個 promise 是 error 就 cancel 所有的 fetch request ,可以查看 racePromiseWithCancelFetchRequest 這個 functions console.log(result) } catch (e) { console.log(e) } } ``` ```typescript // 成功機率是 100% const PromiseAllSettled = async () => { try { const result = await Promise.allSettled([promise(), promise(), promise()]) // 每個 promise 都會執行,allSettled 不會 throw error 而是會顯示 promise 的 status 以及 reason 如下 // [{ status: 'rejected', reason: 'promise reject in 590ms' }, // { status: 'rejected', reason: 'promise reject in 416ms' }, // { status: 'fulfilled', value: 'promise resolve in 539ms' }] console.log(result) } catch (e) { console.log(e) } } ``` ```typescript // 成功機率是 1 - 0.5 * 0.5 * 0.5 = 0.875 const PromiseAny = async () => { try { const result = await Promise.any([promise(), promise(), promise()]) // 每個 promise 都會執行,any 用於觀察哪個 promise 執行最快,並所有的 promise 都 rejected 才會進行錯誤處理 //並且 error 的 instance 是 AggregateError 代表 All promises were rejected console.log(result) } catch (e) { if (e instanceof AggregateError) { console.log('all promise rejected') } } } ``` ```typescript const LoadPlugin = async () => { // 假設你有兩個來源可以 import ,就可以透過 promise.any 決定哪個 module 優先執行。 const lodash = await Promise.any([ import('https://primary.example.com/lodash'), import('https://secondary.example.com/lodash'), ]); } ``` ```typescript // 只要其中一個 fetch request 有 error 立即停止正在進行的 request const racePromiseWithCancelFetchRequest = async () => { const controller = new AbortController(); try { await Promise.race([ fetch('/api/1', { signal: controller.signal }), fetch('/api/2', { signal: controller.signal }), fetch('/api/3', { signal: controller.signal }), new Promise((_, reject) => setTimeout(reject, 1000)) ]) } catch (e) { controller.abort() } } ``` ## 總結 不管是 `promise.any` 或是 `promise.all` 等等所有的 `promise` 都會去執行,差別只是在於 `return` 的結果,與總體的執行時間。 ## AggregateError `AggregateError` 常用於表示一個多個 `error` 的物件,用於 `promise.any` ```typescript try { throw new AggregateError([new Error("some error")], "Hello"); } catch (e) { console.log(e instanceof AggregateError); // true console.log(e.message); // "Hello" console.log(e.name); // "AggregateError" console.log(e.errors); // [ Error: "some error" ] } ``` ## promise like 判斷一個值是否是 `promiseLike` ,`promise` 是 `es6` 添加的語法,在 `promise` 出現以前先有` promise a+` 規則去定義什麼是 `promise` ,所以我們其實自己寫一個構造函數只要符合` promise A+` 規範,也可以當作 `promise` 來使用,所以才會延伸出 `promiseLike` 的 `interface` 。 @link https://promisesaplus.com/ 1. “promise” is an object or function with a then method whose behavior conforms to this specification. 1. “thenable” is an object or function that defines a then method. 1. “value” is any legal JavaScript value(including undefined, a thenable, or a promise). 1. “exception” is a value that is thrown using the throw statement. 1. “reason” is a value that indicates why a promise was rejected. ```typescript function isPromiseLike(value: any) { // 1 if (value !== null && (typeof value === 'object' || typeof value === 'function')) { try { // 2 if (typeof value.then === 'function') { const result = value.then() // 3 if ( ( typeof result === 'object' || typeof result === 'function' || typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean' ) || typeof result === 'undefined') return true } } } catch (e) { // 4 5 return true } } return false } const actualPromise = new Promise((resolve, reject) => { setTimeout(() => resolve('Actual Promise'), 500) }) const mockThenable = { then(resolve: any, reject: any) {}, } const mockThenable2 = { then(resolve: any, reject: any) { return undefined }, } const mockThenable3 = { then(resolve: any, reject: any) { return null }, } const mockThenable4 = { then(resolve: any, reject: any) { throw new Error('Actual Promise') }, } const mockThenable5 = { then(resolve: any, reject: any) { return new Date() }, } const nonPromiseObject = { key: 'value' } const nonPromiseFunction = function () { } console.log(isPromiseLike(actualPromise))// true console.log(isPromiseLike(mockThenable))// true console.log(isPromiseLike(mockThenable2))// true console.log(isPromiseLike(mockThenable3))// false console.log(isPromiseLike(mockThenable4))// true console.log(isPromiseLike(mockThenable5))// true console.log(isPromiseLike(nonPromiseObject)) // false console.log(isPromiseLike(nonPromiseFunction)) // false ```