此篇文章主要是用來加深本身對promise原理的理解。es6
Promise是JS解決異步編程的一種方式,是ES6推廣的一個新概念。所謂的Promise至關於一個狀態機,裏面承載着將來某一個時刻所發生的狀態。編程
Promise的有三種狀態:數組
Promise和回調函數是2個概念,Promise是新的概念而不是回調函數的擴展。promise
使用Promise解決了回調地獄的問題markdown
Promise經過鏈式調用解決了回調函數層層嵌套的問題,異步
使用Promise處理異步編程,不會使得代碼邏輯跳躍異步編程
最原始的回調函數處理異步編程使得代碼跳躍,由於當異步有告終果纔會去執行回調函數,那麼callback的執行和定義可能並不在同一個地方。使得代碼的可讀性沒那麼清晰。而Promsie經過.then函數調用就能夠解決這樣的邏輯跳躍問題。函數
使用Promsie能夠捕獲處理錯誤信息oop
最原始的回調函數處理異步編程沒法實現錯誤信息的捕獲,那麼經過使用Promise的.catch方法或者在.then的第二個參數(回調函數)來處理錯誤信息。測試
在實現Promsie的過程中,須要先了解Promises/A+規範,這裏可查看Promises/A+規範翻譯
let p=new Promise((resolve, reject) => {
setTimeout(() => {
resolve('FULFILLED');
}, 1000)
}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
複製代碼
如上是使用ES6中Promise的基本結構,可見Promise是一個構造函數或者是一個類,傳入一個回調函數,回調函數接收resolve和reject兩個方法;而且Promise有.then和.catch基本這些基本的方法。
promise.then(onFulfilled, onRejected)
複製代碼
一個Promise必須擁有then方法,並且then方法接收2個可選參數onFulfilled和onRejected。
一個Promise必須擁有catch方法,用來處理拒絕的狀況。
promise.catch(onRejected)
複製代碼
Promise有三個狀態值,因此先定義三個狀態常量。一個Promise的當前狀態必須是等待狀態(Pending), 接受狀態(Fulfilled) 和 拒絕狀態(Rejected)當中的一種。
// 先定義三個常量表示狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
複製代碼
MyPromise接收一個fn(函數),執行這個fn,而且將resolve和reject傳遞出去。
MyPromise經過使用try--catch來捕獲錯誤信息。
function MyPromise(fn) {
this.status = PENDING; // 初始狀態爲pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 存一下this,以便resolve和reject裏面訪問
var that = this;
// resolve方法參數是value
function resolve(value) {
if (that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
}
}
// reject方法參數是reason
function reject(reason) {
if (that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
}
}
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
複製代碼
接下來實現一個then函數:
根據上面Promises/A+規範的分析可見,then方法返回一個 promise ,而且then方法能夠處理不一樣狀態值的狀況。想一想當咱們使用promise時,若是成功以後就會調用then的onFulfilled方法,當失敗以後就會調用then的onRejected方法。因此,在大致上能肯定的是有條件分支是當狀態值爲FULFILLED和REJECTED這兩種狀況,在想一想狀態值不一樣的時候,是如何調用onFulfilled方法和onRejected方法。
在調用onFulfilled方法和onRejected方法以前會去判斷onFulfilled方法和onRejected方法是否是一個函數,若是是才調用這個函數,不是則直接返回值。(值是promise1的值是根據成功或者失敗決定返回reason仍是value)。其實就是至關於將promise1的值穿透到promise2。
那若是onFulfilled方法和onRejected方法是函數而且有本身的返回值,那麼咱們就要根據Promises/A+規範進行編碼處理。
在將Promises/A+規範轉換成代碼以前,咱們先看看以下的代碼:
new Promise(fn).then(onFulfilled, onRejected);
複製代碼
當咱們以如上代碼使用promise時,Promise的狀態值仍是 PENDING (then方法和Promise構造函數一塊兒執行,根本不知道何時狀態值發生改變)。這時候確定不能當即調onFulfilled或者onRejected的,由於fn到底成功仍是失敗還不知道。那何時知道fn成功仍是失敗呢?答案是fn裏面主動調resolve或者reject的時候。因此要先收集onFulfilled和onRejected方法(確定在then方法裏面去收集);若是狀態值仍是PENDING,應該將onFulfilled和onRejected兩個回調存起來,等到fn有告終論,resolve或者reject的時候再來調用對應的代碼(確定在狀態值發生變化的地方去調用函數)。由於後面then還有鏈式調用,會有多個onFulfilled和onRejected,這裏用兩個數組將他們存起來,等resolve或者reject的時候將數組裏面的所有方法拿出來執行一遍。
這個過程像極了訂閱發佈模式。那這樣的話then方法裏面確定須要多一條狀態值爲PENDING的條件分支。
因此新增MyPromise函數的代碼以下:
function MyPromise(fn) {
...
// 構造函數裏面添加兩個數組存儲成功和失敗的回調
+ this.onFulfilledCallbacks = [];
+ this.onRejectedCallbacks = [];
// 存一下this,以便resolve和reject裏面訪問
var that = this;
// resolve方法參數是value
function resolve(value) {
if (that.status === PENDING) {
...
// resolve裏面將全部成功的回調拿出來執行
+ that.onFulfilledCallbacks.forEach((callback) => {
+ callback(that.value);
+ });
}
}
// reject方法參數是reason
function reject(reason) {
if (that.status === PENDING) {
...
// resolve裏面將全部失敗的回調拿出來執行
+ that.onRejectedCallbacks.forEach((callback) => {
+ callback(that.reason);
+ });
}
}
...
}
複製代碼
回到當onFulfilled方法和onRejected方法是函數而且有本身的返回值時的狀況,這時候就須要關注Promises/A+規範的Promise 解決過程,將 Promise 解決過程轉成代碼以下:
function resolvePromise(promise, x, resolve, reject) {
// 若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
// 這是爲了防止死循環
if (promise === x) {
return reject(
new TypeError("The promise and the return value are the same")
);
}
if (x instanceof MyPromise) {
// 若是 x 爲 Promise ,則使 promise 接受 x 的狀態
// 也就是繼續執行x,若是執行的時候拿到一個y,還要繼續解析y
// 這個if跟下面判斷then而後拿到執行其實重複了,無關緊要
x.then(function(y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 若是 x 爲對象或者函數
else if (typeof x === "object" || typeof x === "function") {
// 這個坑是跑測試的時候發現的,若是x是null,應該直接resolve
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 賦值給 then
var then = x.then;
} catch (error) {
// 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
return reject(error);
}
// 若是 then 是函數
if (typeof then === "function") {
var called = false;
// 將 x 做爲函數的做用域 this 調用之
// 傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise
// 名字重名了,我直接用匿名函數了
try {
then.call(
x,
// 若是 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y)
function(y) {
// 若是 resolvePromise 和 rejectPromise 均被調用,
// 或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
// 實現這條須要前面加一個變量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
function(r) {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
// 若是調用 then 方法拋出了異常 e:
// 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
if (called) return;
// 不然以 e 爲據因拒絕 promise
reject(error);
}
} else {
// 若是 then 不是函數,以 x 爲參數執行 promise
resolve(x);
}
} else {
// 若是 x 不爲對象或者函數,以 x 爲參數執行 promise
resolve(x);
}
}
複製代碼
在完成Promise 解決過程以後,咱們還需討論onFulfilled 和 onRejected 方法在如何執行?是同步執行仍是異步執行呢?
在規範中講到:實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。
因此在咱們執行onFulfilled 和 onRejected的時候都應該包到setTimeout裏面去。那麼then 方法的實現代碼以下:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 若是onFulfilled不是函數,給一個默認函數,返回value
var realOnFulfilled = onFulfilled;
// 若是onRejected不是函數,給一個默認函數,返回reason的Error
var realOnRejected = onRejected;
var that = this; // 保存一下this
if (this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
setTimeout(function() {
try {
if (typeof onFulfilled !== "function") {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if (this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
setTimeout(function() {
try {
if (typeof onRejected !== "function") {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
// 若是仍是PENDING狀態,將回調保存下來
if (this.status === PENDING) {
var promise2 = new MyPromise(function(resolve, reject) {
that.onFulfilledCallbacks.push(function() {
setTimeout(function() {
try {
if (typeof onFulfilled !== "function") {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function() {
setTimeout(function() {
try {
if (typeof onRejected !== "function") {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
});
return promise2;
}
};
複製代碼
根據ES6的文檔Promise 對象的用法手寫Promise的其餘方法,這裏先拎出來各方法的特色再編寫代碼。
1.MyPromise.resolve特色
MyPromise.resolve = function(parameter) {
if (parameter instanceof MyPromise) {
return parameter;
}
return new MyPromise(function(resolve) {
resolve(parameter);
});
};
複製代碼
MyPromise.reject = function(reason) {
return new MyPromise(function(resolve, reject) {
reject(reason);
});
};
複製代碼
3.MyPromise.all特色
MyPromise.all = function(args=[]) {
let count=0;
let callbackArr=[];
return new Promise((resolve,reject)=>{
for(let i=0;i<args.length;i++){
// (function(i){
args[i].then(res=>{
callbackArr[i]=res;
count++;
if(count==args.length){
return resolve(callbackArr);
}
}).catch(err=>{
return reject(err);
})
//})(i)
}
})
};
複製代碼
MyPromise.prototype.catch 特色
MyPromise.prototype.catch = function(onRejected) {
this.then(null, onRejected);
};
複製代碼
其餘的方法小夥伴自行實現,抓住特色,我以爲實現其餘就不會很難了。
知原理知天下,當會使用一個語法的時候多考慮背後的實現思想原理,會加深對此語法的理解。