最近在項目中又遇到了一個接口的請求需要依賴另一個接口的請求結果和處理高並發的場景了,所以即興在這里簡單總結和分享下,歡迎指正和補充。
一、Promise 簡要回顧
Promise 是一個構造函數,作為 ES6 中最重要的特性之一,它有 all、resolve、reject、race ... 眼熟的方法,原型上有 then、catch 等同樣熟悉的方法,所以 new Promise 返回的 promise 對象也有 then、catch 方法。

我們知道 Promise 能幫助我們避免回調地獄,可以理解成 Promise 就是對於回調函數的另一種寫法。雖然從表面上看,Promise 能夠簡化層層回調的寫法,但 Promise 通過傳遞狀態的方式來使得回調函數能夠及時調用,它比傳統的回調函數和事件更簡單、更靈活。
以下以定時器模擬,對於多個請求情況的處理,實例化如下:
function getP1() { console.log('getP1開始調用') return new Promise((resolve, reject) =>{ setTimeout(function(){ console.log('getP1調用完成'); resolve('getP1調用成功'); }, 1000); }); } function getP2() { console.log('getP2開始調用') return new Promise((resolve, reject) => { setTimeout(function(){ console.log('getP2調用完成'); resolve('getP2調用成功'); }, 2000); }); } function getP3() { console.log('getP3開始調用') return new Promise((resolve, reject) => { setTimeout(function(){ console.log('getP3調用完成'); resolve('getP3調用成功'); }, 3000); }); }
二、使用場景 ①
僅調用,不考慮其他,只要執行了即可。
getP1().then(res1 => { console.log('getP1結果:' + res1) })
getP2().then(res2 => { console.log('getP2結果:'+ res2) }) ......
三、使用場景 ②
先調用 p1,再調用 p2,再調用 p3 ......
getP1() .then(res1 => { console.log('getP1結果:' + res1) return getP2() }) .then(res2 => { console.log('getP2結果:' + res2) return getP3() }) .then(res3 => { console.log('getP3結果:' + res3) })
運行結果如下:

從結果可以看出,像這樣按順序,每隔一秒輸出每個異步回調中的內容,就是鏈式調用。具體代碼執行順序:
1)第一步調用 getP1 函數,執行完成返回一個 promise,狀態是成功,即 resolve('getP1調用成功'),然后將參數 "getP1調用成功" 傳遞並執行第一個 then;
2)第一個 then 接收 "getP1調用成功",執行 then 的回調,回調中調用了 getP2,執行並返回成功狀態的 promise,然后給下一個 then 傳遞參數 "getP2調用成功";
3)第二個 then 接收 "getP2調用成功",執行 then 的回調,回調中調用了 getP3,執行並返回成功狀態的 promise,然后給下一個 then 傳遞參數 "getP3調用成功";
4)最后一個 then 接收 "getP3調用成功",執行回調,然后輸出 "getP3調用成功"。
注意:
-
-
如果需要滿足鏈式調用,.then 方法中必須返回一個 promise 對象。
-
.then 在鏈式調用時,會等其前一個 then 中的回調函數執行完畢,並且返回成功狀態的 promise,才會執行下一個 then 的回調函數,而且 .then 方法的參數就是上一個 .then 方法中 resolve 的參數。
所以鏈式調用比較常用的一個場景就是,當下一個操作依賴於上一個操作的結果或狀態的時候,可以很方便地通過 .then 方法的參數來傳遞數據。
-
四、reject 的使用
前面的例子只有執行成功的回調,沒有失敗的情況,可以通過 reject 把 Promise 的狀態置為 rejected(代碼拋出異常或出錯了),然后在 .catch 方法中捕捉到執行失敗情況的回調。例如:
function getP2() { console.log('getP2開始調用') return new Promise((resolve, reject) =>{ setTimeout(function(){ console.log('getP2調用完成'); // 這里偷懶直接這樣寫了,可以添加一些處理判斷邏輯等 reject('getP2調用失敗'); }, 2000); }); } getP1() .then(res1 => { console.log('getP1結果:' + res1) return getP2() }) .then(res2 => { console.log('getP2結果:' + res2) return getP3() }) .then(res3 => { console.log('getP3結果:' + res3) }) .catch(err => { console.log('error:' + err) })
執行結果如下:

每一個.then 都是銜接着上一個 promise 的,.catch 會捕捉任意一個 promise 的 reject 狀態。當 .then 返回一個 rejected 的 promise 時,后面就不執行了,拋出的錯誤也會被 .catch 方法捕獲。
五、使用場景 ③
p3 的調用依賴 p1 和 p2 的執行結果
這就用到了 Promise 的 all 方法,它提供了並行執行異步操作的能力,並且在所有異步操作執行完后才執行回調。
Promise.all 接收一個 promise 對象的數組作為參數,當這個數組里的所有 promise 對象全部變為 resolve 或 reject 狀態的時候,它才會去調用 .then 方法。看下面的例子(此處仍使用上面定義好的getP1、getP2、getP3 這三個函數):
Promise .all([getP1(), getP2(), getP3()]) .then(res => { // 返回的 res 由 getP1,getP2,getP3 返回的結果所構成的數組 console.log(res); });
上面代碼的輸出結果如下:

有了 all 方法,我們就可以並行執行多個異步操作,並且在一個回調中處理所有的返回數據,就是上面的 res。比如打開網頁時,需要預先加載用到的資源如圖片或靜態文件,等所有的都加載完后,我們再進行頁面的初始化。
如果把 getP2 的 Promise 狀態設置為 reject,執行如下代碼:
Promise.all([getP1(), getP2(), getP3()])
.then(res => { console.log('all:' + res) }) .catch(err => { console.log('err: ' + err) })
執行結果如下:

注意:
-
getP1,getP2,getP3 的狀態都是 resolve 的時候,Promise.all 的狀態才會變成 resolve;
-
getP1,getP2,getP3 中只要有一個的狀態為 reject,Promise.all 的狀態就會變成 reject,后面的就不會再執行了;
-
回調函數中的返回數據,和傳遞的 promise 數組的順序是一致的。
六、Promise.all() 方法的使用情景:
-
p3 依賴 p1 和 p2,p1 和 p2 之間異步執行
Promise.all([getP1(), getP2()]) .then(([res1, res2]) => { console.log('res1:' + res1) console.log('res2:' + res2) return getP3() }) .then(res => { console.log(res) })
結果如下:

-
p3 依賴 p1 和 p2,p1 和 p2 同步執行
Promise.all([await getP1(), await getP2()]) .then(([res1, res2]) => { console.log('res1:' + res1) console.log('res2:' + res2) return getP3() }) .then(res => { console.log(res) })
結果:

七、axios 處理高並發
相似的,在官方 axios 中,提供了 axios.all() 和 axios.spread() 兩個函數,用於處理同時發送多個並發請求,在多個請求都完成后再執行一些邏輯。此處借用官方文檔的示例:
function getUserAccount() { return axios.get('/user/12345'); } function getUserPermissions() { return axios.get('/user/12345/permissions'); } axios.all([getUserAccount(), getUserPermissions()]) .then( axios.spread((acct, perms) => { // 兩個請求都完成后 // acct -> getUserAccount 的返回值 // perms -> getUserPermissions 的返回值 }) );
注意:
-
- axios.all() 方法接收一個數組作為參數,數組的每個元素都是一個請求,返回一個 promise 對象;
-
當數組中所有請求執行完成后,才執行 axios.spread() 中的函數,且 axios.spread() 回調函數中返回值的順序和請求的順序一致。
可以看到 axios.all() 方法與 Promise.all() 方法,不管在使用方式還是傳參形式都是一模一樣的。
