前端異步一直是個熱門話題,做爲新一代異步編程解決方案的Promise
,相比傳統異步編程,不只解決了恐怖的回調地獄,在寫法上也是至關方便簡潔。如此牛*的功能讓人不由地想一探究竟,在各大網絡公司面試中,手寫Promise也是常見的筆試題了,就讓咱們一塊兒來簡單探討一下Promise的實現吧!javascript
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。前端
// 定義一個Promise對象
let promise = new Promise((resolve,reject) => {
true&&resolve(true) || reject(false)
});
promise.then(res=>{
console.log(res)
}).catch(err=>{
throw err
})
複製代碼
如此一個低級簡單的Promise就完成了。java
resolve
和reject
,構造函數內部須要一個狀態變量state
(pending、resolved、rejected),一個異步成功的回參res
then
方法,有兩個參數分別是onFulfilled
和onRejected
catch
、finally
等一系列。。。。。。既然是手寫,那麼咱們直接將原來的Promise對象覆蓋,這裏採用ES6的class寫法。面試
class Promise {
// 構造函數
constructor(executor){
// 狀態控制state,默認值值爲pending、成功resolved、失敗rejected
this.state = 'pending';
// 成功返回值
this.res = '';
// 失敗返回緣由
this.reason = '';
// resolve方法
let resolve = (res) => {
// 若是狀態state爲pending,將狀態變爲resolved,並將返回值賦給res
this.state === 'pending' && (this.state = 'resolved') && (this.res = res);
};
let reject = (reason) => {
// 若是狀態state爲pending,將狀態變爲rejected,並返回錯誤緣由
this.state === 'pending' && (this.state = 'rejected') && (this.reason = reason);
};
// 若是executor報錯的話,直接執行reject
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
}
複製代碼
then(onFulfilled,onRejected){
// state爲resolved,執行onFulfilled
this.state === 'resolved' && (()=>{
onFulfilled(this.res)
})();
// state爲rejected,執行onRejected
this.state === 'rejected' && (()=>{
onRjected(this.reason)
})();
}
複製代碼
此時,一個最基本最簡單最低級的Promise就完成了,讓咱們先測試一下結果,隨機一個數,若是大於0.5則成功,若是小於則失敗。編程
let promise = new Promise((resolve,reject)=>{
let random = Math.random();
if(random>0.5){
resolve(random)
} else{
reject(random)
}
});
promise.then(res=>{
console.log('成功:' + res)
},err=>{
console.log('失敗:' + err)
})
複製代碼
刷新控制檯:數組
看起來沒什麼毛病,不過若是你認爲這就結束了?promise
有人問,Promise不就是解決異步的,怎麼又說到異步,這裏要說的異步就是指定時器了,好比setTimeout
,爲何這樣說,這就涉及到js的事件循環機制(eventLoop)了,這裏暫且不提。網絡
先看一個例子:dom
let promise = new Promise((resolve,reject)=>{
let random = Math.random();
if(random>0.5){
setTimeout(() => {
resolve(random)
}, 1000);
} else{
setTimeout(() => {
reject(random)
}, 1000);
}
});
promise.then(res=>{
console.log('成功:' + res)
},err=>{
console.log('失敗:' + err)
})
複製代碼
此時,無論怎麼刷新,控制檯就是不打印結果,無論正確仍是錯誤,這是爲何呢?異步
首先咱們定義了一個Promise對象的實例,在實例中定義了一個定時器,一秒後返回,當運行到定時器以後,定時器掛起,繼續執行下面的promise.then,而此時由於定時器的緣故,無論是resolve仍是reject都沒有運行,也就是說state的狀態任然沒有改變,因此在then方法中,沒有對應的項,若是再次執行then方法,同理依然沒法運行,那麼問題來了,如何解決由於定時器致使的狀態未變化和屢次執行then方法呢?
首先爲了屢次執行then方法,咱們定義兩個空數組,分別存放成功或者失敗方法,而後在then方法中,若是狀態依然是pending,則將成功或者失敗的函數存入對應的數組,當定時器時間過了以後,在resolve或者reject中循環對應數組並執行函數。
constructor(executor){
// 原先代碼不變
// ......
// 添加數組
// 成功列表
this.resolvedList = [];
// 失敗列表
this.rejectedList = [];
// 改造一下resolve和reject
let resolve = (res) => {
this.state === 'pending' && (this.state = 'resolved') && (this.res = res);
this.state === 'resolved' && this.resolvedList.forEach(fn => {
fn();
})
};
// 失敗
let reject = (reason) => {
this.state === 'pending' && (this.state = 'rejected') && (this.reason = reason);
this.state === 'rejected' && this.rejectedList.forEach(fn => {
fn();
})
};
}
複製代碼
因爲定時器的緣由,形成先執行then,那麼在then中將對應函數存起:
then(onFulfilled,onRejected){
this.state === 'pending' && (() =>{
this.resolvedList.push(()=>{
console.log('resolvedList.push')
onFulfilled(this.res);
});
this.rejectedList.push(()=>{
onRejected(this.reason)
});
return
})()
// state爲resolved,執行onFulfilled
this.state === 'resolved' && (()=>{
onFulfilled(this.res)
})();
// state爲rejected,執行onRejected
this.state === 'rejected' && (()=>{
onRjected(this.reason)
})();
}
複製代碼
調用then方法
promise.then(
res => {
console.log("成功1:" + res);
},
err => {
console.log("失敗1:" + err);
}
);
promise.then(
res => {
console.log("成功2:" + res);
},
err => {
console.log("失敗2:" + err);
}
);
複製代碼
刷新控制檯:
此時控制檯已經能夠正常打印。
爲了解決回調地獄,在Promise中,咱們是這樣的寫法:new Promise().then().then()
,道理其實很簡單,第一個then方法返回的值也是一個Promise對象,那麼就能繼續使用then方法。
當咱們在第一個then中return了一個參數(參數未知,需判斷)。這個return出來的新的promise就是onFulfilled()或onRejected()的值:
callBackPromise
成功的結果callBackPromise
成功的結果function callBackFunction(callBackPromise,res, resolve, reject){
// 判斷callBackPromise === res ,若是等於會形成死循環
if(callBackPromise === res){
return reject(new TypeError('注意死循環了!'));
}
// 防止重複調用
let reCall;
if(res !== null && (typeof res === 'Object' || typeof res === 'function')){
try {
// 鏈式,調用上一次結果的then
let then = res.then;
typeof then === 'function' && then.call(res, nextRes => {
if(reCall){
return
}
reCall = true;
callBackPromise(callBackPromise,nextRes,resolve,reject)
},err => {
if(reCall)
return;
reject(err);
});
resolve(res)
}catch(e){
if(reCall)
return;
reject(e)
}
}else{
reject(res)
// resolve(res)
}
}
複製代碼
then(onFulfilled,onRejected){
// 注意此處用到的var,若是用let會形成callBackFunction報錯,緣由是let沒有變量提高
var callBackPromise = new Promise((resolve,reject) => {
this.state === 'pending' && (() =>{
this.resolvedList.push(()=>{
let res = onFulfilled(this.res);
callBackFunction(callBackPromise,res,resolve,reject)
});
this.rejectedList.push(()=>{
let res = onRejected(this.reason)
callBackFunction(callBackPromise,res,resolve,reject)
});
return
})()
this.state === 'resolved' && (()=>{
let res = onFulfilled(this.res);
callBackFunction(callBackPromise,res,resolve,reject)
})();
this.state === 'rejected' && (()=>{
let res = onRejected(this.reason);
callBackFunction(callBackPromise,res,resolve,reject)
})();
})
return callBackPromise;
}
複製代碼
測試一下,若是打印爲空多刷新幾回,由於沒有實現catch方法:
promise.then(res => {
console.log('第一次回調',res)
return '第一次返回'
})
.then(res2=>{
console.log('第二次回調',res2)
return '第二次回調'
})
.then(res3=>{
console.log('第三次回調',res3)
})
複製代碼
能夠看到此時已經能夠進行鏈式操做。
到此,一個基本的鏈式調用已經完成,然而Promise的博大精深遠不及此,諸如onFulfilled,onRejected的可選性,catch、finally、all等方法的實現都尚未完成,須要不斷完善,若是上述代碼中有錯誤的還請各位大佬指出,輕噴。