前言
都 2020 年了,Promise 大家肯定都在用了陌兑,但是估計(jì)很多人對(duì)其原理還是一知半解允瞧,今天就讓我們一起實(shí)現(xiàn)一個(gè)符合 PromiseA+ 規(guī)范的 Promise。
簡單版
我們都知道 Promise 的調(diào)用方式泌绣,new Promise(executor)钮追, executor 兩個(gè)參數(shù)预厌,resolve,reject元媚。所以現(xiàn)在我們的代碼長這樣
class Promise {
constructor(executor) {
const resolve = () => {}
const reject = () => {}
executor(resolve, rejcet)
}
}
Promise 內(nèi)部有三個(gè)狀態(tài)轧叽,pending、fulfilled刊棕、rejected炭晒,初始是 pending,調(diào)用 resolve 后變?yōu)?fulfilled,甥角,調(diào)用 reject 后變?yōu)?rejected网严。fulfilled 時(shí)會(huì)調(diào)用 then 注冊(cè)的成功的回調(diào),rejected 時(shí)會(huì)調(diào)用 then 注冊(cè)的失敗的回調(diào)嗤无。
// Promise 內(nèi)部狀態(tài)
const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }
class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.value = undefined; // 成過的值
this.reason = undefined; // 失敗的值
const resolve = (val) => {
if (this.status == STATUS.PENDING) {
this.status = STATUS.FUFILLED;
this.value = val;
}
}
const reject = (reason) => {
if (this.status == STATUS.PENDING) {
this.status = STATUS.REJECTED;
this.reason = reason;
}
}
try {
executor(resolve, reject);
} catch (e) {
// 出錯(cuò)走失敗邏輯
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.status == STATUS.FUFILLED) {
onFulfilled(this.value);
}
if (this.status == STATUS.REJECTED) {
onRejected(this.reason);
}
}
}
現(xiàn)在我們的 Promise 已經(jīng)初步實(shí)現(xiàn)了震束,但還有很多問題
一個(gè) promise 可以調(diào)用多次 then 方法怜庸,也就是說可以注冊(cè)多個(gè)回調(diào),所以我們需要一個(gè)隊(duì)列來保存這些回調(diào)垢村。同時(shí)我們沒有對(duì) pending 狀態(tài)的 then 方法做處理割疾,當(dāng) promise 為 pending 狀態(tài)時(shí),then 方法應(yīng)該將回調(diào)放入到隊(duì)列當(dāng)中嘉栓,而不是直接運(yùn)行宏榕。所以改進(jìn)之后的代碼如下。
const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }
class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.value = undefined; // 成過的值
this.reason = undefined; // 失敗的值
+ this.onResolvedCallbacks = []; // 存放成功的回調(diào)的
+ this.onRejectedCallbacks = []; // 存放失敗的回調(diào)的
const resolve = (val) => {
if (this.status == STATUS.PENDING) {
this.status = STATUS.FUFILLED;
this.value = val;
// 成功時(shí)調(diào)用成功隊(duì)列里的回調(diào)
+ this.onResolvedCallbacks.forEach(fn=>fn());
}
}
const reject = (reason) => {
if (this.status == STATUS.PENDING) {
this.status = STATUS.REJECTED;
this.reason = reason;
// 失敗時(shí)調(diào)用失敗隊(duì)列里的回調(diào)
+ this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve, reject);
} catch (e) {
// 出錯(cuò)走失敗邏輯
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.status === STATUS.FUFILLED) {
onFulfilled(this.value);
}
if (this.status === STATUS.REJECTED) {
onRejected(this.reason);
}
+ if (this.status === STATUS.PENDING) {
+ this.onResolvedCallbacks.push(()=>{ // todo..
+ onFulfilled(this.value);
+ })
+ this.onRejectedCallbacks.push(()=>{ // todo..
+ onRejected(this.reason);
+ })
+ }
+ }
}
到這一個(gè)簡單 promise 80%的功能已經(jīng)實(shí)現(xiàn)了侵佃,但是還有一個(gè)問題麻昼,promise 可以鏈?zhǔn)秸{(diào)用,也就是我們巢霰玻看到的 promise.then().then()涌献。所以我們得在 then 方法里去返回一個(gè)新的 promise。
const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }
class Promise {
// 上面邏輯省略
...
then(onFulfilled, onRejected) { // swtich 作用域
+ let promise2 = new Promise((resolve, reject) => {
+ if (this.status === STATUS.FUFILLED) {
+ // to....
+ try {
+ let x = onFulfilled(this.value);
+ resolve(x);
+ } catch (e) {
+ reject(e);
+ }
+ }
+ if (this.status === STATUS.REJECTED) {
+ try {
+ let x = onRejected(this.reason);
+ resolve(x);
+ } catch (e) {
+ reject(e);
+ }
+ }
+ if (this.status === STATUS.PENDING) {
+ this.onResolvedCallbacks.push(() => { // todo..
+ try {
+ let x = onFulfilled(this.value);
+ resolve(x);
+ } catch (e) {
+ reject(e);
+ }
+ })
+ this.onRejectedCallbacks.push(() => { // todo..
+ try {
+ let x = onRejected(this.reason);
+ resolve(x);
+ } catch (e) {
+ reject(e);
+ }
+
+ })
+ }
+ })
+
+ return promise2;
+ }
}
我們注意到我們把回調(diào)的執(zhí)行邏輯都放到了 promise2 的內(nèi)部首有,之所以這樣做燕垃,是因?yàn)槲覀冃枰?onFufilled 的返回值去 resolve promise2,這也是為什么 then 回調(diào)的返回值會(huì)傳給下一個(gè) then 的原因井联。
完整版
上面的 promise 與規(guī)范有一些差距
then 注冊(cè)的回調(diào)都是異步執(zhí)行的
如果 then 注冊(cè)回調(diào)的返回值是個(gè)函數(shù)或?qū)ο蟛泛荆@里處理起來會(huì)復(fù)雜一點(diǎn),我們先看看規(guī)范是怎么定義的
promise2 = promise1.then(onFulfilled, onRejected);
x = onFulfilled 或 onRejected 的返回值
- 2.3.1 如果promise和x引用同一個(gè)對(duì)象烙常,則用TypeError作為原因拒絕(reject)promise轴捎。
- 2.3.2 如果x是一個(gè)promise,采用promise的狀態(tài)
- 2.3.2.1 如果x是請(qǐng)求狀態(tài)(pending),promise必須保持pending直到xfulfilled或rejected
- 2.3.2.2 如果x是完成態(tài)(fulfilled),用相同的值完成fulfillpromise
- 2.3.2.2 如果x是拒絕態(tài)(rejected)蚕脏,用相同的原因rejectpromise
- 2.3.3另外侦副,如果x是個(gè)對(duì)象或者方法
- 2.3.3.1 讓x作為x.then.
- 2.3.3.2 如果取回的x.then屬性的結(jié)果為一個(gè)異常e,用e作為原因reject promise
- 2.3.3.3 如果then是一個(gè)方法,把x當(dāng)作this來調(diào)用它驼鞭,第一個(gè)參數(shù)為resolvePromise秦驯,第二個(gè)參數(shù)為rejectPromise,其中:
- 2.3.3.3.1 如果/當(dāng) resolvePromise 被一個(gè)值 y 調(diào)用,運(yùn)行 [[Resolve]](promise, y)
- 2.3.3.3.2 如果/當(dāng) rejectPromise 被一個(gè)原因 r 調(diào)用挣棕,用 r 拒絕(reject)promise
- 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都被調(diào)用译隘,或者對(duì)同一個(gè)參數(shù)進(jìn)行多次調(diào)用,第一次調(diào)用執(zhí)行洛心,任何進(jìn)一步的調(diào)用都被忽略
- 2.3.3.3.4 如果調(diào)用 then 拋出一個(gè)異常 e
- 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已被調(diào)用固耘,忽略。
- 2.3.3.3.4.2 或者词身, 用 e 作為reason拒絕(reject)promise
規(guī)范可能有點(diǎn)復(fù)雜厅目,需要自己慢慢消化,這里我直接把代碼貼出來,我會(huì)在代碼里標(biāo)注每個(gè)規(guī)范的實(shí)現(xiàn)點(diǎn)损敷。
const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }
// 我們的promise 按照規(guī)范來寫 就可以和別人的promise公用
function resolvePromise(x, promise2, resolve, reject) {
// 規(guī)范 2.3.1
if (promise2 == x) { // 防止自己等待自己完成
return reject(new TypeError('出錯(cuò)了'))
}
// 規(guī)范 2.3.3
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// x可以是一個(gè)對(duì)象 或者是函數(shù)
let called;
try {
// 規(guī)范 2.3.3.1
let then = x.then;
if (typeof then == 'function') {
// 2.3.3.3
then.call(x, function(y) {
// 規(guī)范 2.3.3.3.3
if (called) return
called = true;
// 規(guī)范 2.3.3.3.1
resolvePromise(y, promise2, resolve, reject);
}, function(r) {
// 規(guī)范 2.3.3.3.3
if (called) return
called = true;
// 規(guī)范 2.3.3.3.2
reject(r);
})
} else {
resolve(x); // 此時(shí)x 就是一個(gè)普通對(duì)象
}
} catch (e) {
// 規(guī)范 2.3.3.3.4.1
if (called) return
called = true;
// 規(guī)范 2.3.3.3.4
reject(e); // 取then時(shí)拋出錯(cuò)誤了
}
} else {
resolve(x); // x是一個(gè)原始數(shù)據(jù)類型 不能是promise
}
// 不是proimise 直接就調(diào)用resolve
}
class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = []; // 存放成功的回調(diào)的
this.onRejectedCallbacks = []; // 存放失敗的回調(diào)的
const resolve = (val) => {
if(val instanceof Promise){ // 是promise 就繼續(xù)遞歸解析
return val.then(resolve,reject)
}
if (this.status == STATUS.PENDING) {
this.status = STATUS.FUFILLED;
this.value = val;
// 發(fā)布
this.onResolvedCallbacks.forEach(fn => fn());
}
}
const reject = (reason) => {
if (this.status == STATUS.PENDING) {
this.status = STATUS.REJECTED;
this.reason = reason;
// 腹部
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (e) {
// 出錯(cuò)走失敗邏輯
reject(e)
}
}
then(onFulfilled, onRejected) { // swtich 作用域
// 可選參數(shù)
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function'? onRejected: err=> {throw err}
let promise2 = new Promise((resolve, reject) => {
if (this.status === STATUS.FUFILLED) {
// to....
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === STATUS.REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === STATUS.PENDING) {
// 裝飾模式 切片編程
this.onResolvedCallbacks.push(() => { // todo..
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e);
}
}, 0);
})
this.onRejectedCallbacks.push(() => { // todo..
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e);
}
}, 0);
})
}
});
return promise2;
}
}
測試工具
給大家推薦一個(gè)測試 promise 是否規(guī)范的工具 --- promises-aplus-tests户辫,使用方法如下
全局安裝 promises-aplus-tests,然后添加以下代碼
Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject
})
return dfd;
}
module.exports = Promise
然后直接在在控制臺(tái)運(yùn)行 promises-aplus-tests <當(dāng)前 promise 代碼地址>
可以看到我們的 promise 是順利通過測試的嗤锉。
總結(jié)
希望這篇文章可以幫助大家更深入的理解 promise
碼字不易渔欢,期望得到你的贊。