0、前言
????????處在前后端分離開發模式的時代,前端向后端請求數據似乎已經司空見慣,在稍微復雜一點的業務中就可能遇到串行接口的情況,這就會產生回調嵌套,回調過深會導致代碼可維護性差,因此需要一種解決回調過深的方案:promise。ES6 promise語法精深,本文只介紹工作中最常用到的promise相關知識。
1、promise語法
- 創建promise
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//實例
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
創建promise最重要的操作就是確定何時執行resolve方法
- Promise.prototype.then方法
- 雖然then方法接受兩個函數作為參數,但一般只寫成功回調,并用catch代替失敗回調
//bad
promise.then(successFn,errorFn)
//good
promise.then(successFn).catch(errorFn)
- 根據successFn返回的結果,then有不同的返回值,then方法總是返回一個新的promise:
2.1 當successFn返回普通值v1時,then返回原來的promise,并將v1作為后續then方法的成功回調參數;
2.2 當successFn返回已經被resolved的promise時,then返回原來的promise,并將successFn返回的promise值作為后續then方法的成功回調參數;
2.3 當successFn返回一個未被resolved的promise1時,then方法返回的promise將與promise1具有一致的狀態,可以看成then方法返回了promise1
- Promise.prototype.resolve方法
根據resolve方法接受的參數類型分為:
1、promise,直接返回這個promise
2、普通類型(不包含then方法的對象),返回一個立即resolve的promise
3、不傳參數,返回一個立即resolve的promise - Promise.prototype.all方法
接受promise組成的數組,并能使結果有序返回。缺點是一旦其中一個promise被rejected,所有數據都拿不到了。返回的結果以數組形式存儲,可按下標依次取出
2、promise運用場景
1、單個請求promise化
//異步加載圖片
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
loadImageAsync(url).then(successFn).catch(handleError)
2、多個串行請求promise化
場景:先拿用戶信息,再獲取數據
//方案1
getUserInfo().then(getData).then(successFn).catch(handleError)
方案2
async function fn(){
let userInfo=await getUserInfo();
let data=await getData(userInfo);
return data;
}
fn().then(successFn).catch(errorFn);
方案2的async函數寫法使得串行的鏈式調用變成了同步寫法,語義更加清楚
3、多個并行請求promise化
場景:一次性拿不同tab下的數據
方案1:使用Promise.all()方法
let tab1_promise=getHotData();
let tab2_promise=getLatestData();
let arr=[tab1_promsie,tab2_promise];
Promise.all(arr).then(([tab1_data,tab2_data])=>handleFn).catch(errorFn);
這種方式顯然有風險,可考慮如下方案2
方案2:順序執行promise
let arr=[tab1_promise,tab2_promise];
arr.reduce((task,promise)=>{
return task.then(()=>return promise).then(successFn);
},Promise.resolve());
這種方案可以對每個promise的返回值單獨處理,不必等到所有數據一起返回才處理,更加靈活
3、總結
- 創建一個promise的關鍵在于確定resolve的執行時機
- then方法總是返回一個新的promise
- 使用這種寫法promise.then(successFn).catch(errorFn),而不是promise.then(successFn,errorFn)
- Promise.reslove()可以創造一個立即resolve的promise,結合reduce方法可使代碼更加精煉
- async函數基于Promise,可解決串行鏈式調用過長的問題,并且語義清楚,推薦使用
- Promise.all方法有風險,盡量不用
4、超時和可取消的Promise
// 可取消的Promise(本質上并沒有取消請求,只是不用promise的返回值而已)
export default function makeCancelable(promise){
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then((val) =>
hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
);
promise.catch((error) =>
hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
}
// 超時的Promise
export const makeTimeoutPromise = ({ timeout = 300000, callback,promise }) => {
// 默認5分鐘超時
let hasTimeout = false;
let timer = setTimeout(() => {
hasTimeout = true;
}, timeout);
const wrappedPromise = new Promise((resolve, reject) => {
promise.then((val) =>
hasTimeout ? reject({hasTimeout: true}) : resolve(val)
).catch((error) =>
hasTimeout ? reject({hasTimeout: true}) : reject(error)
).finally(()=>timer && clearTimeout(timer))
}
return wrappedPromise;
};
掌握以上這些,應該能夠應對工作中JS的異步處理了
https://mp.weixin.qq.com/s/cN40pHBfttZ3O2oEbVPAcg