傳統的非同步回撥程式設計最大的缺陷是:回撥地獄,由於業務邏輯非常複雜,程式碼序列請求好幾層;並行請求以前也要通過引用step、async庫實現。現在ES6推出了Promise,通過Promise的鏈式呼叫可以解決回撥地獄問題,通過Promise.all方法可以解決並行請求的問題。現在我們通過手寫Promise來徹底理解Promise的原理。
一、構造簡單Promise函式,實現Promise的then方法
先來看一下簡單的使用方法:
var promise=new Promise(function(resolved,rejected){
console.log(1);
resolved('123');
})
promise.then(function(data){
console.log('success'+data)
},function(err){
console.log('fail'+err)
})
複製程式碼
注意點:
- new Promise傳入的executor方法程式碼是同步執行的;
- promise物件的狀態有三種:pending(等待態),resolved(成功態),rejected(失敗態),只能從等待態轉化成成功態或失敗態;
- executor中執行resolve()方法,表示將轉化為成功態,promise.then呼叫時執行成功的方法,executor中執行reject()方法,表示將轉化為成功態,promise.then呼叫時執行失敗的方法。
思路:
- 首先構造一個類Promise,傳入一個引數executor方法;
- 用status記錄Promise狀態,預設是pending,成功態是resolved,失敗態是rejected, execuotor執行時,重寫resolve、reject方法,執行到這兩個方法時,將promise狀態修改,並將引數儲存起來,以便then方法呼叫; 3.當例項執行then方法時,依據promise狀態來執行成功方法或失敗方法。
這樣就實現了這個簡單的peomise,程式碼如下:
function Promise(executor){
let self=this;
self.status='pending';
self.value=undefined;
self.reason=undefined;
function resolve(value){
if(self.status==='pending'){
self.value=value;
self.status='resolved';
}
}
function reject(reason){
if(self.status==='pending'){
self.reason=reason;
self.status='rejected';
}
}
try{
executor(resolve,reject) //第一步:先執行executor,根據裡面resolve和reject的呼叫,執行上面的resolve和reject方法
}catch(e){
reject(e);
}
}
Promise.prototype.then=function(onFulfilled,onRejected){
let self=this;//第二步:then被呼叫的時候根據已經記錄的promise狀態,執行成功方法或失敗方法
if(self.status==='resolved'){
onFulfilled(self.value);
}
if(self.status==='rejected'){
onRejected(self.reason);
}
}
module.exports = Promise
複製程式碼
二、實現Promise executor中的非同步呼叫
先來看一下簡單使用小例子:
var promise=new Promise(function(resolve,reject){
console.log('hello')
setTimeout(function(){
resolve('ok')
},1000);
})
promise.then(function(data){
console.log('success'+data)
},function(err){
console.log('fail'+err)
})
複製程式碼
上面例子,先列印出hello,過1秒後執行resolve方法,列印出successok。
注意點:
- executor執行一秒之後再呼叫resolve方法,才會執行then中的成功方法;
思路:
- executor中方法沒呼叫resolve之前,Promise方法一直是pending狀態,這時候執行器已經執行完了then方法,這時我們把then方法中的成功方法和失敗方法存入陣列,當resolve方法呼叫時,去執行這些儲存起來的方法;
- 沒有非同步呼叫的話,executor執行時執行resolve方法,成功陣列 為空,所以不會被影響,還是在then方法中去執行成功或失敗方法。
function Promise(executor){
let self=this;
self.status='pending';
self.value=undefined;
self.reason=undefined;
self.onResolvedCallbacks=[];
self.onRejectedCallbacks=[];
function resolve(value){
if(self.status==='pending'){
self.value=value;
self.status='resolved';
self.onResolvedCallbacks.forEach(item=>item());//最終執行resolve方法時,修改狀態,將成功陣列中的方法取出,一一執行。
}
}
function reject(reason){
if(self.status==='pending'){
self.reason=reason;
self.status='rejected';
self.onRejectedCallbacks.forEach(item=>item());//最終執行reject方法時,修改狀態,將失敗陣列中的方法取出,一一執行。
}
}
try{
executor(resolve,reject)
}catch(e){
reject(e);
}
}
Promise.prototype.then=function(onFulfilled,onRejected){
let self=this;
if(self.status==='resolved'){
onFulfilled(self.value);
}
if(self.status==='rejected'){
onRejected(self.reason);
}
if(self.status==='pending'){ //執行then的時候是等待態,就將成功方法存入onResolvedCallbacks陣列,將失敗方法存入onRejectedCallbacks陣列
self.onResolvedCallbacks.push(function(){
onFulfilled(self.value);
});
self.onRejectedCallbacks.push(function(){
onRejected(self.reason);
});
}
}
module.exports = Promise
複製程式碼
三、實現Promise then的鏈式呼叫
鏈式呼叫是Promise中的核心方法,也是最重要的一個方法,先來看一下鏈式呼叫的用法:
let p=new Promise((resolve,reject)=>{
console.log(1);
setTimeout(function(){
resolve('123');
},1000);
})
let p2=p.then((data)=>{
console.log(data);
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('456');
},1000);
});
},err=>{
console.log(err);
throw Error('失敗');
}).then((data)=>{
console.log('aaa'+data);
},(err)=>{
console.log(err);
})
複製程式碼
上面程式碼首先列印出executor中的1,1秒之後執行resolve方法執行第一個then中的成功方法,先列印出123,再執行此方法中return的Promise,一秒之後執行下一個then的成功方法,列印出aaa456。
注意點:
- 如果then方法返回一個普通值,執行下一個then的成功方法;
- 如果丟擲一個錯誤,執行下一個then的失敗方法;
- then方法中返回一個Promise物件,等待Promise物件執行成功就呼叫下個then的成功方法,這個Promise呼叫reject()就執行下一個then的失敗方法;
思路:
1.在then方法後之所以可以接著呼叫then方法,肯定then方法需要返回一個Promise例項;
- 在then方法中我們用x去接收成功或失敗方法的返回值,當成功或失敗方法丟擲錯誤,直接執行reject,呼叫下一個then的失敗方法,專門寫了一個方法resolvePromise分析返回值;
- resolvePromise方法中傳入了四個引數,then中的Promise2,x值;
- 如果x與promise相等,報型別錯誤迴圈引用;
- 當x不是null,是個物件型別或者是個函式型別,並且他的then方法也是函式型別時,我們認為x是一個promise例項,執行x.then()方法;再用resolvePromise方法分析他的成功的返回值;
- 如果x不是null不是object或function,我們認為x是一個普通值,執行promise2中的resolve方法;
- 如果x.then方法獲取失敗,呼叫promise2的reject方法;
- 定義一個called為true,當執行過resolve或reject方法後將其變為false,保證resolve和reject方法只執行一次;
- 這裡面存在兩個遞迴呼叫,一個是then方法裡面return new Promise方法,一個是resolvePromise方法,第一個的遞迴呼叫解決了executor中非同步呼叫的問題,第二個遞迴呼叫解決了return Promise例項的問題。
程式碼如下:
function Promise(executor){
let self=this;
self.status='pending';
self.value=undefined;
self.reason=undefined;
self.onResolvedCallbacks = []; // 存放then成功的回撥
self.onRejectedCallbacks = []; // 存放then失敗的回撥
function resolve(value){
if(self.status==='pending'){ //這裡面的self不能用this替代,因為是回撥執行的時候不是在類裡面所以這裡的this不是類函式
self.status='resolve';
self.value=value;
self.onResolvedCallbacks.forEach(item=>{item()})
}
}
function reject(reason){
if(self.status==='pending'){
self.status='reject';
self.reason=reason;
self.onRejectedCallbacks.forEach(item=>{item()})
}
}
try{
executor(resolve,reject);
}catch(e){
reject(e);
}
}
function resolvePromise(promise2,x,resolve,reject){
if(promise2===x){//自己等於自己,報型別錯誤
return reject(new TypeError('迴圈引用了'));
}
let called;//控制成功或失敗只能呼叫一次
if(x!==null && (typeof x==='object'||typeof x==='function')){
try{
let then=x.then;
if(typeof then==='function'){
then.call(x,function(y){ //y可能還是一個promise,遞迴解析,直到返回普通值
if(called)return;
called=true;
resolvePromise(promise2,y,resolve,reject)
},function(err){
if(called)return;
called=true;
reject(err);
});
}else{
resolve(x);
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值
resolve(x);
}
}
Promise.prototype.then=function(onFufilled,onReject){
onFufilled=typeof onFufilled==='function'?onFufilled:function(value){
return value;
}
onReject=typeof onReject==='function'?onReject:function(err){
throw err;//丟擲錯誤,才會走下個then的失敗
}
let self=this;
let promise2;
if(self.status=='pending'){
promise2=new Promise(function(resolve,reject){
self.onResolvedCallbacks.push(function(){
setTimeout(function(){
try{
let x=onFufilled(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e)
}
})
});
self.onRejectedCallbacks.push(function(){
setTimeout(function(){
try{
let x=onReject(self.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
})
}
if(self.status=='resolve'){
promise2=new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x=onFufilled(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e)
}
})
})
}
if(self.status=='reject'){
promise2=new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x=onReject(self.reason);//這裡的x可能是一個普通值,也可能是一個promise,所以寫個方法統一處理。
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
})
}
return promise2;
};
複製程式碼
四、檢測一下我們的Promise是否符合Promise A+規範
我們需要先給Promise類增加一個defer方法,其實就是一個語法糖。
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
複製程式碼
沒有上面的語法糖,無法被檢測。 下載一個外掛npm install -g promises-aplus-tests 在命令列工具中執行命令:npm install -g promises-aplus-tests ./promise.js 即可檢驗自己的promise寫的如何。
五、附贈Promise的all、race、resolve、reject、catch方法
Promise.all=function(promises){
//promises是一個promise的陣列,同時需要返回一個promise
return new Promise(function(resolve,reject){
let arr=[]; //arr是最終返回值的結果集
let i=0;
function processData(index,y){
arr[index]=y;
if(++i===promises.length){
resolve(arr);
}
}
for(let i=0;i<promises.length;i++){ //因為then的時候,for迴圈已經走了,i值就是最大的,所以這裡用let,保證每次i都是都是當前值
promises[i].then(function(y){
processData(i,y);
},function(err){
reject();
})
}
})
}
Promise.race=function(promises){
return new Promise(function(resolve,reject){
for(let i=0;i<promises.length;i++){
promises[i].then(function(data){
resolve(data);
},function(err){
reject(err);
})
}
})
}
Promise.resolve=function(value){
return new Promise(function(resolve,reject){
resolve(value);
})
}
Promise.reject=function(reason){
return new Promise(function(resolve,reject){
reject(reason);
})
}
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}
複製程式碼