
1. 項目概述為什么我們需要深入理解Promise如果你寫過JavaScript尤其是處理過異步操作那你一定繞不開Promise。它早已不是ES6時代的新鮮玩意兒而是現代前端開發的基石。但說實話我見過太多開發者包括一些工作兩三年的朋友對Promise的理解還停留在“.then能拿到結果.catch能捕獲錯誤”的層面。當面試官問到“Promise.all和Promise.allSettled有什么區別”或者“如何實現一個Promise.race”時往往就卡殼了。這其實挺危險的。Promise不僅僅是語法糖它是一套完整的異步編程模型。理解不透徹寫出來的代碼就容易埋下隱患——比如內存泄漏、未處理的Promise拒絕就是那個煩人的“Uncaught (in promise)”錯誤、或者并發控制不當導致頁面卡頓。我接手過不少遺留項目里面充斥著層層嵌套的.then和混亂的錯誤處理維護起來簡直是一場噩夢。所以我決定寫這篇東西。這不是一篇簡單的API文檔翻譯而是把我這些年踩過的坑、總結的最佳實踐以及那些面試常考、工作中必用的九個核心靜態方法all, race, allSettled, any, resolve, reject, try, withResolvers掰開揉碎了講給你聽。我們會從最基礎的“Promise是什么”聊起深入到每個方法的特性、返回值、使用場景和那些容易翻車的細節。目標是讓你讀完以后不僅能應付面試更能寫出健壯、優雅的異步代碼。2. Promise核心概念與狀態機理解“承諾”的本質在深入方法之前我們必須把Promise的“心法”搞清楚。很多人用Promise卻不知道它內部是怎么運轉的這就好比開車不懂發動機原理平路還好一到復雜路況就容易熄火。2.1 Promise的三種狀態與不可逆性一個Promise對象只可能處于三種狀態之一待定Pending初始狀態既沒有被兌現也沒有被拒絕。已兌現Fulfilled意味著操作成功完成。此時Promise會有一個不可變的兌現值Fulfillment Value。已拒絕Rejected意味著操作失敗。此時Promise會有一個不可變的拒絕原因Rejection Reason。這里最關鍵的兩個詞是“不可變Immutable”和“不可逆”。一旦狀態從Pending變為Fulfilled或Rejected它就再也不會改變了并且這個結果值或原因也會被永久固定下來。這是Promise可靠性的基石。你可以把它想象成一份法律合同。合同簽訂后Pending最終要么執行成功Fulfilled拿到約定的報酬Value要么執行失敗Rejected并附上違約的理由Reason。合同一旦執行完畢結果就無法更改了。2.2 Thenable鏈與微任務隊列Promise的.then()、.catch()和.finally()方法會返回一個新的Promise。這正是鏈式調用的基礎。但更重要的是這些回調函數不是立即執行的它們會被排入一個叫做**微任務隊列Microtask Queue**的地方。注意微任務隊列的優先級高于瀏覽器渲染和宏任務如setTimeout、事件回調。這意味著在一個事件循環中所有同步代碼執行完后會清空整個微任務隊列然后才進行渲染或執行下一個宏任務。這個機制保證了Promise回調的及時性也是實現一些精細異步控制的關鍵。console.log(腳本開始); // 1. 同步任務 Promise.resolve().then(() { console.log(Promise 1 回調); // 3. 微任務 }); setTimeout(() { console.log(setTimeout 回調); // 4. 宏任務 }, 0); console.log(腳本結束); // 2. 同步任務 // 輸出順序腳本開始 - 腳本結束 - Promise 1 回調 - setTimeout 回調理解這個執行順序對于調試異步代碼的時序問題至關重要。2.3 錯誤處理的“冒泡”機制Promise的錯誤處理有一個非常強大的特性穿透Propagation。如果一個Promise被拒絕并且在其鏈上沒有對應的.catch()處理程序這個拒絕狀態會一直向下傳遞直到被捕獲為止。如果始終沒被捕獲在瀏覽器中就會導致“Uncaught (in promise) Error”的警告。Promise.reject(new Error(出錯了)) .then(() console.log(這里不會執行)) .then(() console.log(這里也不會執行)) .catch(err console.error(錯誤在這里被捕獲, err)); // 錯誤會“冒泡”到這里這個機制讓我們可以像使用try...catch一樣在異步調用鏈的末尾統一處理錯誤讓代碼更清晰。但這也要求我們必須有意識地添加錯誤處理否則就是埋雷。3. 核心靜態方法一并發控制四巨頭——all, race, allSettled, any這四位是處理多個Promise并發場景的“四大天王”功能強大但各有脾性用錯了場景效果大打折扣。3.1 Promise.all全部成功否則失敗使用場景當你需要等待多個獨立的異步操作全部完成并且這些操作的結果相互依賴后續邏輯需要所有結果時。例如同時請求用戶信息、訂單列表和消息通知等所有數據都拿到后再渲染頁面。特性與返回值接收一個可迭代對象通常是Promise數組。返回一個新的Promise。只有所有輸入的Promise都成功兌現返回的Promise才會以數組形式兌現數組元素順序與輸入順序嚴格一致。如果其中任何一個Promise被拒絕則返回的Promise會立即拒絕拒絕原因為第一個被拒絕的Promise的原因。這就是所謂的“快速失敗Fail-fast”。const p1 Promise.resolve(1); const p2 Promise.resolve(2); const p3 Promise.reject(new Error(第三個失敗了)); Promise.all([p1, p2, p3]) .then(results console.log(results)) // 不會執行 .catch(err console.error(捕獲到錯誤, err.message)); // 輸出捕獲到錯誤 第三個失敗了 // 成功案例 Promise.all([p1, p2]) .then(([result1, result2]) { // 使用解構賦值 console.log(結果1: ${result1}, 結果2: ${result2}); // 輸出結果1: 1, 結果2: 2 });實操心得與避坑指南注意輸入類型Promise.all的參數可以是任何可迭代對象不一定是數組。如果傳入非Promise值它會用Promise.resolve()進行包裝。“快速失敗”的副作用這是優點也是缺點。優點是能第一時間發現錯誤缺點是如果其他Promise還在執行比如網絡請求它們不會被取消可能會繼續消耗資源并在后臺完成或失敗只是結果被忽略了。在需要確保所有操作都嘗試完成的場景如批量提交這可能不是最佳選擇。內存考慮如果并發量極大比如上萬個PromisePromise.all會同時發起所有請求可能導致內存激增或觸發瀏覽器并發限制。此時需要考慮分片chunk處理。3.2 Promise.race誰快聽誰的使用場景為異步操作設置超時或者從多個冗余數據源中獲取數據取最先返回的那個。例如從主服務器和備用服務器同時請求同一份數據用先返回的結果。特性與返回值接收一個可迭代對象。返回一個新的Promise。這個Promise的最終狀態兌現或拒絕與第一個敲定Settled即非Pending狀態的輸入Promise的狀態完全相同并采用其值或原因。// 超時控制經典模式 function fetchWithTimeout(url, timeout 5000) { const fetchPromise fetch(url); const timeoutPromise new Promise((_, reject) { setTimeout(() reject(new Error(請求超時)), timeout); }); return Promise.race([fetchPromise, timeoutPromise]); } fetchWithTimeout(https://api.example.com/data) .then(response response.json()) .then(data console.log(data)) .catch(err console.error(請求失敗或超時, err));實操心得與避坑指南“落敗者”的命運和Promise.all一樣其他“跑得慢”的Promise并不會被取消或停止。它們會繼續執行直到完成只是其結果被忽略了。如果這些操作有副作用如寫入數據庫需要額外小心。小心“立即敲定”的Promise如果傳入的數組中包含一個已經拒絕的Promise比如Promise.reject(...)那么Promise.race幾乎會立即拒絕因為一個已敲定的Promise就是“最快的”。這在組合使用時需要留意。不是“競速成功”Promise.race只認第一個敲定的無論成功還是失敗。如果你需要的是“第一個成功的”那應該用Promise.any。3.3 Promise.allSettled悉數匯報不論成敗使用場景ES2020引入。當你需要知道所有異步操作最終的結果無論每個操作是成功還是失敗時。典型場景是批量操作后的結果匯總報告比如同時向多個API發送監控數據需要知道每個發送是否成功而不是一個失敗就全盤放棄。特性與返回值接收一個可迭代對象。永遠不會拒絕。它會等待所有輸入的Promise都敲定Settled。返回一個Promise其兌現值是一個對象數組。每個對象描述一個輸入Promise的最終結果。每個結果對象都有一個status屬性值為fulfilled或rejected。如果status為fulfilled則對象會有一個value屬性包含兌現值。如果status為rejected則對象會有一個reason屬性包含拒絕原因。const promises [ Promise.resolve(成功1), Promise.reject(new Error(失敗1)), Promise.resolve(成功2), ]; Promise.allSettled(promises) .then(results { console.log(所有操作已完成); results.forEach((result, index) { if (result.status fulfilled) { console.log( 操作 ${index}: 成功值 ${result.value}); } else { console.log( 操作 ${index}: 失敗原因 ${result.reason.message}); } }); // 可以進一步處理過濾出成功的或記錄所有失敗 const successfulOps results.filter(r r.status fulfilled); const failedOps results.filter(r r.status rejected); });實操心得與避坑指南與Promise.all的核心區別all是“一損俱損”allSettled是“各自為政匯總上報”。在需要確保所有操作都執行完畢的場景下allSettled是更安全的選擇。結果處理由于返回格式固定處理起來非常結構化。你可以輕松地統計成功/失敗數量或對失敗的操作進行重試。兼容性雖然現在主流環境都支持但如果需要支持非常老的瀏覽器或Node.js版本可能需要polyfill。3.4 Promise.any取第一個成功的使用場景ES2021引入。當你需要多個操作中的任何一個成功即可并且你只關心成功的那個結果。如果所有操作都失敗你才認為整體失敗。典型例子是從多個CDN源或鏡像服務器加載一個資源用第一個可用的。特性與返回值接收一個可迭代對象。返回一個新的Promise。只要有一個輸入的Promise成功兌現返回的Promise就會立即以該成功值兌現。如果所有輸入的Promise都被拒絕則返回的Promise會以一個特殊的AggregateError拒絕該錯誤的errors屬性包含了所有輸入Promise的拒絕原因數組。const primarySource fetch(https://primary.cdn.com/asset.js).catch(() { throw new Error(主源失敗) }); const backupSource1 fetch(https://backup1.cdn.com/asset.js).catch(() { throw new Error(備份源1失敗) }); const backupSource2 fetch(https://backup2.cdn.com/asset.js).catch(() { throw new Error(備份源2失敗) }); Promise.any([primarySource, backupSource1, backupSource2]) .then(response { console.log(資源加載成功來自, response.url); return response.text(); }) .catch(err { // 如果全部失敗err是一個AggregateError console.error(所有源都失敗了); if (err instanceof AggregateError) { err.errors.forEach((e, i) console.error( 錯誤${i}: ${e.message})); } });實操心得與避坑指南與Promise.race的對比這是最容易混淆的一對。race是“第一個出結果的無論成敗”any是“第一個成功的”。在超時場景用race在多源擇優場景用any。空數組或全為拒絕如果傳入空數組或者所有Promise都立即拒絕Promise.any會同步返回一個被拒絕的Promise拒絕原因為AggregateError。AggregateError處理這是JavaScript新的內置錯誤類型。在處理Promise.any的失敗時記得檢查錯誤類型以便訪問所有失敗原因這對于調試非常有用。4. 核心靜態方法二工具與工廠方法除了處理并發的“四大天王”Promise還提供了一些用于創建和包裝的靜態方法它們像工具箱里的螺絲刀雖小但不可或缺。4.1 Promise.resolve 與 Promise.reject快速創建這兩個方法用于快速創建一個狀態已確定的Promise。Promise.resolve(value)創建一個以給定值兌現的Promise。如果value本身就是一個Promise則直接返回這個Promise行為類似于Promise.resolve(Promise.resolve(x))返回一個兌現為x的Promise。如果value是一個thenable對象即具有.then方法的對象則會“展開unwrap”它。Promise.reject(reason)創建一個以給定原因拒絕的Promise。// 快速創建已解決的Promise常用于起始值或測試 const cachedData Promise.resolve({ name: 緩存數據 }); // 統一接口返回Promise function getData(id) { if (!id) { // 快速返回一個拒絕的Promise比 throw 更適用于異步函數上下文 return Promise.reject(new Error(ID不能為空)); } return fetch(/api/data/${id}); } // Promise.resolve 展開 thenable const thenable { then: function(onFulfill, onReject) { onFulfill(來自thenable的值); } }; Promise.resolve(thenable).then(val console.log(val)); // 輸出來自thenable的值實操心得Promise.resolve()常用于將同步值或第三方thenable庫如jQuery的Deferred轉換為標準的ES6 Promise保證接口一致性。在異步函數async function中return Promise.reject(err)和throw err效果幾乎一樣但前者在某些鏈式調用中意圖更明確。4.2 Promise.try同步異常的異步化處理這是一個目前處于ECMAScript提案階段Stage 3的方法但思想非常重要并且有流行的polyfill如Bluebird庫。它的目的是讓一個可能拋出同步異常的函數安全地在Promise上下文中啟動。為什么需要它考慮以下代碼function riskyOperation(data) { if (!data) { throw new Error(數據無效); // 同步拋出錯誤 } return doAsyncWork(data); // 返回一個Promise } // 錯誤寫法同步錯誤無法被.catch捕獲 Promise.resolve() .then(() riskyOperation(null)) .catch(err console.error(捕獲不到同步錯誤, err)); // 這里的.catch抓不到上面的throw // 傳統解決方式用try...catch包裝 Promise.resolve() .then(() { try { return riskyOperation(null); } catch (err) { return Promise.reject(err); // 將同步錯誤轉換為Promise拒絕 } }) .catch(err console.error(現在能捕獲了, err));Promise.try或類似的實現就是為了簡化這個模式// 假設有 Promise.try Promise.try(() riskyOperation(null)) .then(result console.log(result)) .catch(err console.error(同步和異步錯誤都能在這里捕獲, err)); // 統一錯誤處理核心價值它保證了傳遞給它的函數中的所有異常無論是同步throw還是異步reject都能被Promise鏈末端的.catch()捕獲實現了錯誤處理的真正統一。雖然原生API還未完全支持但理解這個模式對于編寫健壯的異步代碼至關重要。在實踐中你可以自己寫一個簡單的try包裝函數或者使用提供了此功能的工具庫。4.3 Promise.withResolvers更靈活的手動控制這是ES2024新增的一個非常實用的靜態方法。它解決了一個特定場景的問題當你需要在Promise構造函數外部比如在事件回調中控制Promise的決議resolve或reject時。傳統的new Promise((resolve, reject) { ... })模式resolve和reject函數被限定在構造函數執行器內部。Promise.withResolvers將它們“提取”出來返回一個包含promise、resolve、reject三個屬性的對象。使用場景將基于回調的API或事件驅動API轉換為Promise時特別有用。// 傳統方式將事件監聽轉換為Promise代碼嵌套在executor內 function waitForEvent(element, eventType) { return new Promise((resolve, reject) { const handler (event) { element.removeEventListener(eventType, handler); resolve(event); }; element.addEventListener(eventType, handler); // 如果想支持超時拒絕reject的邏輯也需要寫在這里比較臃腫 }); } // 使用 Promise.withResolvers邏輯更清晰控制權外露 function waitForEventWithResolvers(element, eventType, timeoutMs) { const { promise, resolve, reject } Promise.withResolvers(); const eventHandler (event) { cleanup(); resolve(event); }; const timeoutId timeoutMs ? setTimeout(() { cleanup(); reject(new Error(等待事件超時)); }, timeoutMs) : null; function cleanup() { element.removeEventListener(eventType, eventHandler); if (timeoutId) clearTimeout(timeoutId); } element.addEventListener(eventType, eventHandler); return promise; // 注意resolve和reject被“捕獲”在閉包中外部無法直接訪問保證了安全。 } // 使用 const button document.getElementById(myButton); waitForEventWithResolvers(button, click, 3000) .then(event console.log(按鈕被點擊了, event)) .catch(err console.error(出錯或超時, err));實操心得分離關注點它允許你將Promise的創建、事件監聽綁定、超時設置等邏輯分離開使代碼更模塊化、易讀。謹慎使用因為resolve和reject函數被暴露在更廣的作用域你需要確保它們不會被誤調用或多次調用。通常建議像上面例子一樣將它們封裝在一個函數內部避免泄漏到全局。并非替代new Promise對于簡單的、一次性執行的異步任務new Promise依然是最直接的方式。withResolvers更適合需要將決議控制權“掛起”并在未來某個由外部事件觸發的場景。5. 鏈式調用、錯誤處理與性能陷阱掌握了靜態方法我們再來深入看看Promise鏈式調用的細節和那些容易踩的坑。5.1 Then、Catch、Finally的返回值詳解這是Promise鏈能運轉起來的核心規則必須了然于胸。.then(onFulfilled, onRejected)它返回一個新的Promise記作P2。如果onFulfilled或onRejected函數返回一個值x則P2以該值x兌現。拋出一個異常e則P2以該異常e為原因拒絕。返回一個Promisep則P2將“跟隨”這個Promisep即P2的狀態和值/原因將與p保持一致。如果.then中沒有提供對應的回調例如Promise被拒絕但未提供onRejected那么P2的狀態和值/原因將直接“穿透”到下一個鏈中的Promise。.catch(onRejected)本質上是.then(null, onRejected)或.then(undefined, onRejected)的語法糖。所有關于.then的返回值規則都適用于.catch。.finally(onFinally)它也會返回一個新的Promise。onFinally回調函數不接收任何參數它不知道Promise是成功還是失敗。onFinally函數如果返回一個Promise則會等待該Promise完成。最關鍵的一點.finally返回的Promise其最終狀態和值/原因通常與調用它的原Promise保持一致除非在onFinally回調中拋出了錯誤或返回了一個被拒絕的Promise。Promise.resolve(原始值) .then(val { console.log(val); // 原始值 return val 經過then加工; // 返回一個值新Promise以此值兌現 }) .then(newVal { console.log(newVal); // 原始值 經過then加工 return Promise.resolve(來自另一個Promise的值); // 返回一個Promise新Promise跟隨它 }) .then(val { console.log(val); // 來自另一個Promise的值 throw new Error(主動拋出錯誤); // 拋出異常新Promise以此拒絕 }) .catch(err { console.error(捕獲錯誤, err.message); // 主動拋出錯誤 return 從錯誤中恢復的值; // 在catch中返回一個值鏈將恢復 }) .finally(() { console.log(無論成功失敗finally都會執行); // 這里如果拋出錯誤會覆蓋前面的結果 // throw new Error(Finally里的錯誤); // 取消注釋試試 }) .then(finalVal { console.log(最終值, finalVal); // 從錯誤中恢復的值 });5.2 常見錯誤模式與最佳實踐Promise地獄嵌套then// 壞味道 getData().then(a { processA(a).then(b { processB(b).then(c { console.log(c); }); }); }); // 正確扁平化鏈式調用 getData() .then(a processA(a)) .then(b processB(b)) .then(c console.log(c));忘記返回Promise// 錯誤第二個then接收到的undefined getData() .then(data { // 這里執行了一個異步操作但沒有return saveToDB(data); // saveToDB返回一個Promise }) .then(result { console.log(result); // undefined }); // 正確 getData() .then(data { return saveToDB(data); // 必須return }) .then(result console.log(保存成功ID, result.id));在Promise構造函數中忘記調用resolve/reject 這會導致Promise永遠處于Pending狀態引發內存泄漏和程序邏輯停滯。// 危險 const promise new Promise((resolve, reject) { doAsyncWork((err, data) { if (err) { // 糟糕這里只寫了console.log沒有調用reject console.error(err); } else { resolve(data); } }); });混合使用async/await和傳統Promise鏈雖然可以混用但風格不一致會降低可讀性。通常建議在一個項目或模塊中保持一致風格。5.3 性能考量與內存泄漏鏈過長過長的Promise鏈雖然不會像回調地獄那樣難以閱讀但每個.then都會創建微任務。如果鏈非常長比如成千上萬個可能會輕微影響性能。對于需要循環大量異步操作的情況考慮使用async/await配合循環或者使用專門的異步控制流庫。未處理的Promise拒絕這是最常見的“內存泄漏”和Bug來源之一。一個被拒絕但未被捕獲的Promise其關聯的錯誤和上下文可能無法被垃圾回收。務必為Promise鏈添加最終的.catch()處理或者在頂級使用window.addEventListener(unhandledrejection, ...)瀏覽器環境或process.on(unhandledRejection, ...)Node.js環境來捕獲全局未處理的拒絕。閉包引用在Promise回調或執行器中如果引用了外部的大對象或DOM元素即使Promise已完成這些引用也可能被保持導致內存無法釋放。確保在不再需要時解除引用例如將變量置為null。6. 實戰從面試題看Promise深度理解理論說再多不如來幾道經典的、有代表性的題目練練手。這些題目能很好地檢驗你是否真正理解了Promise的微任務、事件循環和鏈式傳播機制。6.1 題目一基礎執行順序console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); Promise.resolve().then(function() { console.log(promise1); }).then(function() { console.log(promise2); }); console.log(script end);輸出順序script start-script end-promise1-promise2-setTimeout解析同步代碼最先執行。setTimeout是宏任務其回調被放入任務隊列。Promise.resolve().then(...)將兩個微任務放入微任務隊列。同步代碼執行完畢后事件循環會清空整個微任務隊列所以promise1和promise2連續輸出然后才會從宏任務隊列中取出下一個任務執行setTimeout。6.2 題目二混合async/awaitasync function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); async1(); new Promise(function(resolve) { console.log(promise1); resolve(); }).then(function() { console.log(promise2); }); console.log(script end);輸出順序script start-async1 start-async2-promise1-script end-async1 end-promise2-setTimeout解析關鍵點在于await async2();。await之后的代碼console.log(async1 end)相當于被包裝到了async2()返回的Promise的.then()回調里成了一個微任務。所以在同步代碼執行完后微任務隊列里有async1 end和promise2。它們按被加入隊列的順序依次執行。6.3 題目三Promise鏈的返回值Promise.resolve() .then(() { console.log(1); return Promise.resolve(2); // 注意這里返回了一個Promise }) .then(res { console.log(res); }); Promise.resolve() .then(() { console.log(3); }) .then(() { console.log(4); }) .then(() { console.log(5); }) .then(() { console.log(6); });輸出順序1-3-4-2-5-6解析這道題有點刁鉆核心在于**return Promise.resolve(2)會產生額外的微任務**。根據ECMAScript規范當一個Promise的onFulfilled處理程序返回一個thenable包括Promise時需要額外的步驟來“解析resolve”這個thenable這會導致至少一個額外的微任務延遲。第一個鏈輸出1然后返回Promise.resolve(2)這引入了一個額外的微任務我們叫它microtask-X來等待這個Promise敲定。第二個鏈同步輸出3然后4。此時microtask-X完成了它讓第一個鏈的第二個.then可以執行輸出2。接著第二個鏈繼續輸出5和6。6.4 手寫實現Promise.all的要點面試中常要求手寫Promise.all這考察了對Promise狀態、迭代器和并發控制的理解。Promise.myAll function(iterable) { // 1. 參數校驗與轉換 const promises Array.from(iterable); // 將可迭代對象轉為數組 const results new Array(promises.length); let completedCount 0; // 2. 處理空數組情況 if (promises.length 0) { return Promise.resolve([]); } // 3. 返回一個新的Promise return new Promise((resolve, reject) { promises.forEach((promise, index) { // 4. 用Promise.resolve包裝確保處理的是Promise Promise.resolve(promise) .then(value { // 5. 按順序存儲結果 results[index] value; completedCount; // 6. 只有當所有都完成時才resolve最終結果數組 if (completedCount promises.length) { resolve(results); } }) .catch(reason { // 7. 任何一個失敗立即reject快速失敗 reject(reason); }); }); }); }; // 測試 const p1 Promise.resolve(1); const p2 2; // 非Promise值 const p3 new Promise((resolve) setTimeout(() resolve(3), 100)); Promise.myAll([p1, p2, p3]) .then(results console.log(results)) // [1, 2, 3] .catch(err console.error(err));手寫要點處理非Promise輸入使用Promise.resolve()包裝每個元素這是Promise.all的規范行為。保持結果順序利用閉包和索引index確保結果數組的順序與輸入順序一致無論各個Promise完成的先后。快速失敗機制在任何一個Promise拒絕時立即調用外層的reject并傳入該拒絕原因。計數器判斷完成使用計數器completedCount來判斷是否所有Promise都已兌現比檢查results數組的每個元素更高效。邊界條件處理空數組輸入應直接返回一個已兌現的空數組Promise。理解并能手寫這些核心方法意味著你對Promise的運行機制有了扎實的掌握不再是停留在表面API的調用者了。