前言
在javascript的世界中,所有程式碼都是單執行緒執行的。由於這個“缺陷”,導致JavaScript的所有網路操作,瀏覽器事件,都必須是非同步執行。 最開始我們可以用回撥函式來解決這個問題,
function callBack(){
console.log('回撥')
}
setTimeout(callBack, 1000)
// 回撥
複製程式碼
但是隨著業務的不斷深入,難免會像陷入回撥地獄這樣的問題。直到後來我們有了Promise來解決這個問題。
手寫一個promise
promise的基本用法如下: 在例項化一個Promise時,傳入一個函式作為引數,該函式接受兩個引數,分別為resolve,reject.如解決則會列印資料,如被拒絕則會列印拒絕原因
let p1 = new Promise(function (resolve, reject) {
})
p1.then(function (data) {
console.log(data)
}, function (err) {
console.log(err)
})
複製程式碼
相關概念
- 術語
- 解決(fulfill):指一個 promise 成功時進行的一系列操作,如狀態的改變、回撥的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之。
- 拒絕(reject):指一個 promise 失敗時進行的一系列操作。
- 終值(eventual value):所謂終值,指的是 promise 被解決時傳遞給解決回撥的值,由於 promise 有一次性的特徵,因此當這個值被傳遞時,標誌著 promise 等待態的結束,故稱之終值,有時也直接簡稱為值(value)。
- 拒因(reason):也就是拒絕原因,指在 promise 被拒絕時傳遞給拒絕回撥的值
- 執行流程 每個promise後面鏈一個物件該物件包含onfulfiled,onrejected,子promise三個屬性,當父promise 狀態改變完畢,執行完相應的onfulfiled/onfulfiled的時候呢,拿到子promise,在等待這個子promise狀態改變,再執行相應的onfulfiled/onfulfiled。依次迴圈直到當前promise沒有子promise
3.狀態機制切換 如圖所示,狀態只能由pengding-->fulfilled,或者由pending-->rejected這樣轉變。 只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise物件新增回撥函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的
基本結構
function Promise(executor) {
this.state = 'pending'; //狀態
this.value = undefined; //成功結果
this.reason = undefined; //失敗原因
function resolve(value) { }
function reject(reason) { }
executor(resolve, reject) //立即執行
}
複製程式碼
接收一個executor函式,executor函式傳入就執行(當我們示例化一個promise時,executor立即執行),執行完同步或非同步操作後,呼叫它的兩個引數resolve和reject。其中state儲存了promise的狀態,包含三個狀態:等待態(pending)成功態(resolved)和失敗態(rejected)。promise執行成功後的結果由value儲存,失敗後的原因由reason儲存。
完善resolve與reject
- new Promise((resolve, reject)=>{resolve(value)}) resolve為成功,接收引數value,狀態改變為fulfilled,不可再次改變。
- new Promise((resolve, reject)=>{reject(reason)}) reject為失敗,接收引數reason,狀態改變為rejected,不可再次改變。
- 若是executor函式報錯 直接執行reject() 我們可以這樣實現:
function Promise(executor) {
this.state = 'pending'; //狀態
this.value = undefined; //成功結果
this.reason = undefined; //失敗原因
resolve = (value) => {
// state改變,resolve呼叫就會失敗
if (this.state === 'pending') {
// resolve呼叫後,state轉化為成功態
this.state = 'fulfilled';
// 儲存成功的值
this.value = value;
}
}
reject = (reason) => {
// state改變,reject呼叫就會失敗
if (this.state === 'pending') {
// reject呼叫後,state轉化為失敗態
this.state = 'rejected';
// 儲存失敗的原因
this.reason = reason;
}
}
//如果executor執行報錯,直接執行reject
try {
executor(resolve, reject)
} catch (err) {
reject(err) // executor出錯就直接呼叫
}
}
複製程式碼
實現then方法
每一個Promise例項都有一個then方法,接收兩個為函式的引數,它用來處理非同步返回的結果,它是定義在原型上的方法。
Promise.prototype.then = function (onFulfilled, onRejected) {
};
複製程式碼
當promise的狀態發生了變化,不論成功或失敗都會呼叫then方法,因此then方法裡面也會根據不同的狀態來判斷呼叫哪一個回撥函式。 兩個引數的注意事項:
- onFulfilled 和 onRejected 都是可選引數,也就是說可以傳也可以不傳。傳入的回撥函式如不是一個函式型別,可以直接忽略。
- 兩個引數在 promise 執行結束前其不可被呼叫,其呼叫次數不可超過一次。
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.state === 'resolved') {
//判斷引數型別,是函式執行之,如果 onFulfilled 不是函式,其必須被忽略
if (typeof onFulfilled === 'function') {
onFulfilled(this.value); // 傳入成功的值
}
}
// 如果 onRejected 不是函式,其必須被忽略
if (this.state === 'rejected') {
if (typeof onRejected === 'function') {
onRejected(this.reason); // 傳入失敗的原因
}
}
};
複製程式碼
支援非同步實現
上述把promise的基本功能都實現了,但是還是會存在一個問題,就是promise不支援非同步程式碼,當resolve或reject在setTimeout中實現時,呼叫then方法時,此時狀態仍然是pengding,then方法即沒有呼叫onFulfilled也沒有呼叫onRejected,也就執行沒有任何結果。
我們可以參照釋出訂閱模式,在執行then方法時狀態還是狀態還是pengding時,把回撥函式儲存在一個陣列中,當狀態發生改變時依次從陣列中取出執行就好了,首先在類上新增兩個Array型別的陣列,用於存放回撥函式。
function Promise(executor) {
this.state = 'pending'; //狀態
this.value = undefined; //成功結果
this.reason = undefined; //失敗原因
this.onFulfilledFunc = [];//儲存成功回撥
this.onRejectedFunc = [];//儲存失敗回撥
function resolve(value) {
// ....
}
function reject(reason) {
// ....
}
executor(resolve, reject) //立即執行
}
複製程式碼
並修改then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.state === 'pending') {
if (typeof onFulfilled === 'function') {
this.onFulfilledFunc.push(onFulfilled);//儲存回撥
}
if (typeof onRejected === 'function') {
this.onRejectedFunc.push(onRejected);//儲存回撥
}
}
if (this.state === 'resolved') {
//判斷引數型別,是函式執行之,如果 onFulfilled 不是函式,其必須被忽略
if (typeof onFulfilled === 'function') {
onFulfilled(this.value); // 傳入成功的值
}
}
// 如果 onRejected 不是函式,其必須被忽略
if (this.state === 'rejected') {
if (typeof onRejected === 'function') {
onRejected(this.reason); // 傳入失敗的原因
}
}
};
複製程式碼
修改resolve和reject方法:
function Promise(executor) {
// 其他程式碼
function resolve(value) {
// state改變,resolve呼叫就會失敗
if (this.state === 'pending') {
// resolve呼叫後,state轉化為成功態
this.state = 'fulfilled';
// 儲存成功的值
this.value = value;
this.onFulfilledFunc.forEach(fn => fn(value))
}
}
function reject(reason) {
// state改變,reject呼叫就會失敗
if (this.state === 'pending') {
// reject呼叫後,state轉化為失敗態
this.state = 'rejected';
// 儲存失敗的原因
this.reason = reason;
this.onRejectedFunc.forEach(fn => fn(reason))
}
}
// 其他程式碼
}
複製程式碼
到這裡Promise已經支援了非同步操作了。
鏈式呼叫實現
光是實現了非同步操作可不行,我們常常用到new Promise().then().then()這樣的鏈式呼叫來解決回撥地獄。 規範如何定義then方法:
- 每個then方法都返回一個新的Promise物件(原理的核心)
- 如果then方法中顯示地返回了一個Promise物件就以此物件為準,返回它的結果
- 如果then方法中返回的是一個普通值(如Number、String等)就使用此值包裝成一個新的Promise物件返回。
- 如果then方法中沒有return語句,就視為返回一個用Undefined包裝的Promise物件
- 若then方法中出現異常,則呼叫失敗態方法(reject)跳轉到下一個then的onRejected
- 如果then方法沒有傳入任何回撥,則繼續向下傳遞(值的傳遞特性) 總的來說就是不論何時then方法都要返回一個Promise,這樣才能呼叫下一個then方法。我們可以例項化一個promise2返回,將這個promise2返回的值傳遞到下一個then中。
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
// 其他程式碼
}
return promise2;
};
複製程式碼
接下來就處理根據上一個then方法的返回值來生成新Promise物件.
/**
* 解析then返回值與新Promise物件
* @param {Object} promise2 新的Promise物件
* @param {*} x 上一個then的返回值
* @param {Function} resolve promise2的resolve
* @param {Function} reject promise2的reject
*/
function resolvePromise(promise2, x, resolve, reject) {
//...
}
複製程式碼
當then的返回值與新生成的Promise物件為同一個(引用地址相同),狀態永遠為等待態(pending),再也無法成為resolved或是rejected,程式會死掉,則會丟擲TypeError錯誤
let promise2 = p.then(data => {
return promise2;
});
// TypeError: Chaining cycle detected for promise #<Promise>
複製程式碼
因此需要判斷x。
- x不能和新生成的promise物件為同一個
- x 不能是null,可以是物件或者函式(包括promise), 否則是普通值,那麼直接resolve(x)
- 當x是物件或者函式(預設promise)則宣告then,let then = x.then
- 如果取then報錯,則走reject()
- 如果then是個函式,則用call執行then,第一個引數是this,後面是成功的回撥和失敗的回撥,成功和失敗只能呼叫一個 所以設定一個called來防止多次呼叫
- 如果成功的回撥還是pormise,就遞迴繼續解析
小提示: 為什麼取物件上的屬性有報錯的可能?Promise有很多實現(bluebird,Q等),Promises/A+只是一個規範,大家都按此規範來實現Promise才有可能通用,因此所有出錯的可能都要考慮到,假設另一個人實現的Promise物件使用Object.defineProperty()惡意的在取值時拋錯,我們可以防止程式碼出現Bug resolvePromise實現
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) { // 1.x不能等於promise2
reject(new TypeError('Promise發生了迴圈引用'));
}
let called;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 2. 可能是個物件或是函式
try {
let then = x.then;// 3.取出then方法引用
if (typeof then === 'function') { // 此時認為then是一個Promise物件
//then是function,那麼執行Promise
then.call(x, (y) => { // 5.使用x作為this來呼叫then方法,即then裡面的this指向x
if (called) return;
called = true;
// 6.遞迴呼叫,傳入y若是Promise物件,繼續迴圈
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
// 也屬於失敗
if (called) return;
called = true;
reject(e); // 4.取then報錯,直接reject
}
} else {
//否則是個普通值
resolve(x);
}
}
複製程式碼
此時鏈式呼叫支援已經實現,在相應的地方呼叫resolvePromise方法即可。
最後完善
規範還對onFulfilled和onRejected有規定
- onFulfilled返回一個普通的值,成功時直接等於 value => value
- onRejected返回一個普通的值,失敗時如果直接等於 value => value,則會跑到下一個then中的onFulfilled中,所以直接扔出一個錯誤reason => throw err
- onFulfilled或onRejected不能同步被呼叫,必須非同步呼叫。我們就用setTimeout解決非同步問題
- 如果onFulfilled或onRejected報錯,則直接返回reject()
完善then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
// onFulfilled如果不是函式,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函式,就忽略onRejected,直接扔出錯誤
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
if (this.state === 'pending') {
this.onFulfilledFunc.push(() => {
// 非同步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
this.onRejectedFunc.push(() => {
// 非同步
setTimeout(() => {
try {
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
if (this.state === 'fulfilled') {
// 非同步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'rejected') {
// 非同步
setTimeout(() => {
// 如果報錯
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
})
return promise2;
};
複製程式碼
到這裡手寫一個Promise已經全部實現了 完整程式碼
function Promise(executor) {
this.state = 'pending'; //狀態
this.value = undefined; //成功結果
this.reason = undefined; //失敗原因
this.onFulfilledFunc = [];//儲存成功回撥
this.onRejectedFunc = [];//儲存失敗回撥
resolve = (value) => {
// state改變,resolve呼叫就會失敗
if (this.state === 'pending') {
// resolve呼叫後,state轉化為成功態
this.state = 'fulfilled';
// 儲存成功的值
this.value = value;
this.onFulfilledFunc.forEach(fn => fn(value))
}
}
reject = (reason) => {
// state改變,reject呼叫就會失敗
if (this.state === 'pending') {
// reject呼叫後,state轉化為失敗態
this.state = 'rejected';
// 儲存失敗的原因
this.reason = reason;
this.onRejectedFunc.forEach(fn => fn(reason))
}
}
//如果executor執行報錯,直接執行reject
try {
executor(resolve, reject)
} catch (err) {
reject(err) // executor出錯就直接呼叫
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
// onFulfilled如果不是函式,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函式,就忽略onRejected,直接扔出錯誤
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
if (this.state === 'pending') {
this.onFulfilledFunc.push(() => {
// 非同步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
this.onRejectedFunc.push(() => {
// 非同步
setTimeout(() => {
try {
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
if (this.state === 'fulfilled') {
// 非同步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'rejected') {
// 非同步
setTimeout(() => {
// 如果報錯
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
})
return promise2;
};
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Promise發生了迴圈引用'));
}
let called;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//可能是個物件或是函式
try {
let then = x.then;//取出then方法引用
if (typeof then === 'function') { // 認為then是一個Promise物件
//then是function,那麼執行Promise
then.call(x, (y) => {
// 成功和失敗只能呼叫一個
if (called) return;
called = true;
//遞迴呼叫,傳入y若是Promise物件,繼續迴圈
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
// 成功和失敗只能呼叫一個
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
// 也屬於失敗
if (called) return;
called = true;
reject(e);
}
} else {
//否則是個普通值
resolve(x);
}
}
複製程式碼
但是隻用建構函式實現當然是不夠的,我們再用class來實現一個Promise,基本原理同上 class實現
class Promise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn) {
return this.then(null, fn);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
//resolve方法
Promise.resolve = function (val) {
return new Promise((resolve, reject) => {
resolve(val)
});
}
//reject方法
Promise.reject = function (val) {
return new Promise((resolve, reject) => {
reject(val)
});
}
//race方法
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject)
};
})
}
//all方法(獲取所有的promise,都執行then,把結果放到陣列,一起返回)
Promise.all = function (promises) {
let arr = [];
let i = 0;
function processData(index, data) {
arr[index] = data;
i++;
if (i == promises.length) {
resolve(arr);
};
};
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
processData(i, data);
}, reject);
};
});
}
複製程式碼
最終測試
開源社群提供了一個包用於測試我們的程式碼:promises-aplus-tests,安裝這個包然後執行命令列 promises-aplus-tests [js檔名] 即可驗證。別忘了再程式碼後面加上這一段程式碼
// 目前是通過他測試 他會測試一個物件
// 語法糖
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
複製程式碼
參考連結