JavaScript實現(xiàn)手寫promise的示例代碼
背景
promise 作為前端開發(fā)中常用的函數(shù),解決了 js 處理異步時回調(diào)地獄的問題,大家應(yīng)該也不陌生了,今天來學(xué)習(xí)一下 promise 的實現(xiàn)過程,這樣可以加(面)深(試)理(要)解(考)。
需求
我們先來總結(jié)一下 promise 的特性:
使用:
const p1 = new Promise((resolve, reject) => {
console.log('1');
resolve('成功了');
})
console.log("2");
const p2 = p1.then(data => {
console.log('3')
throw new Error('失敗了')
})
const p3 = p2.then(data => {
console.log('success', data)
}, err => {
console.log('4', err)
})


以上的示例可以看出 promise 的一些特點,也就是我們本次要做的事情:
- 在調(diào)用 Promise 時,會返回一個 Promise 對象,包含了一些熟悉和方法(all/resolve/reject/then…)
- 構(gòu)建 Promise 對象時,需要傳入一個 executor 函數(shù),接受兩個參數(shù),分別是resolve和reject,Promise 的主要業(yè)務(wù)流程都在 executor 函數(shù)中執(zhí)行。
- 如果運行在 excutor 函數(shù)中的業(yè)務(wù)執(zhí)行成功了,會調(diào)用 resolve 函數(shù);如果執(zhí)行失敗了,則調(diào)用 reject 函數(shù)。
- promise 有三個狀態(tài):pending,fulfilled,rejected,默認(rèn)是 pending。只能從pending到rejected, 或者從pending到fulfilled,狀態(tài)一旦確認(rèn),就不會再改變;
- promise 有一個then方法,接收兩個參數(shù),分別是成功的回調(diào) onFulfilled, 和失敗的回調(diào) onRejected。
// 三個狀態(tài):PENDING、FULFILLED、REJECTED
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
// 默認(rèn)狀態(tài)為 PENDING
this.status = PENDING;
// 存放成功狀態(tài)的值,默認(rèn)為 undefined
this.value = undefined;
// 存放失敗狀態(tài)的值,默認(rèn)為 undefined
this.reason = undefined;
// 調(diào)用此方法就是成功
let resolve = (value) => {
// 狀態(tài)為 PENDING 時才可以更新狀態(tài),防止 executor 中調(diào)用了兩次 resovle/reject 方法
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
};
// 調(diào)用此方法就是失敗
let reject = (reason) => {
// 狀態(tài)為 PENDING 時才可以更新狀態(tài),防止 executor 中調(diào)用了兩次 resovle/reject 方法
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
};
try {
// 立即執(zhí)行,將 resolve 和 reject 函數(shù)傳給使用者
executor(resolve, reject);
} catch (error) {
// 發(fā)生異常時執(zhí)行失敗邏輯
reject(error);
}
}
// 包含一個 then 方法,并接收兩個參數(shù) onFulfilled、onRejected
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
調(diào)用一下:
const promise = new Promise((resolve, reject) => {
resolve('成功');
}).then(
(data) => {
console.log('success', data)
},
(err) => {
console.log('faild', err)
}
)
這個時候我們很開心,但是別開心的太早,promise 是為了處理異步任務(wù)的,我們來試試異步任務(wù)好不好使:
const promise = new Promise((resolve, reject) => {
// 傳入一個異步操作
setTimeout(() => {
resolve('成功');
},1000);
}).then(
(data) => {
console.log('success', data)
},
(err) => {
console.log('faild', err)
}
)
發(fā)現(xiàn)沒有任何輸出,我們來分析一下為什么:
new Promise 執(zhí)行的時候,這時候異步任務(wù)開始了,接下來直接執(zhí)行 .then 函數(shù),then函數(shù)里的狀態(tài)目前還是 padding,所以就什么也沒執(zhí)行。
那么我們想想應(yīng)該怎么改呢?
我們想要做的就是,當(dāng)異步函數(shù)執(zhí)行完后,也就是觸發(fā) resolve 的時候,再去觸發(fā) .then 函數(shù)執(zhí)行并把 resolve 的參數(shù)傳給 then。
這就是典型的發(fā)布訂閱的模式,可以看我這篇文章。
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 存放成功的回調(diào)
this.onResolvedCallbacks = [];
// 存放失敗的回調(diào)
this.onRejectedCallbacks= [];
let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 依次將對應(yīng)的函數(shù)執(zhí)行
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 依次將對應(yīng)的函數(shù)執(zhí)行
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
if (this.status === PENDING) {
// 如果promise的狀態(tài)是 pending,需要將 onFulfilled 和 onRejected 函數(shù)存放起來,等待狀態(tài)確定后,再依次將對應(yīng)的函數(shù)執(zhí)行
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
});
// 如果promise的狀態(tài)是 pending,需要將 onFulfilled 和 onRejected 函數(shù)存放起來,等待狀態(tài)確定后,再依次將對應(yīng)的函數(shù)執(zhí)行
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
}
這下再進行測試:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
},1000);
}).then(
(data) => {
console.log('success', data)
},
(err) => {
console.log('faild', err)
}
)
1s 后輸出了:

說明成功啦
then的鏈?zhǔn)秸{(diào)用
這里就不分析細節(jié)了,大體思路就是每次 .then 的時候重新創(chuàng)建一個 promise 對象并返回 promise,這樣下一個 then 就能拿到前一個 then 返回的 promise 了。
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
const resolvePromise = (promise2, x, resolve, reject) => {
// 自己等待自己完成是錯誤的實現(xiàn),用一個類型錯誤,結(jié)束掉 promise Promise/A+ 2.3.1
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// Promise/A+ 2.3.3.3.3 只能調(diào)用一次
let called;
// 后續(xù)的條件要嚴(yán)格判斷 保證代碼能和別的庫一起使用
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
// 為了判斷 resolve 過的就不用再 reject 了(比如 reject 和 resolve 同時調(diào)用的時候) Promise/A+ 2.3.3.1
let then = x.then;
if (typeof then === 'function') {
// 不要寫成 x.then,直接 then.call 就可以了 因為 x.then 會再次取值,Object.defineProperty Promise/A+ 2.3.3.3
then.call(x, y => { // 根據(jù) promise 的狀態(tài)決定是成功還是失敗
if (called) return;
called = true;
// 遞歸解析的過程(因為可能 promise 中還有 promise) Promise/A+ 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
}, r => {
// 只要失敗就失敗 Promise/A+ 2.3.3.3.2
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x.then 是個普通值就直接返回 resolve 作為結(jié)果 Promise/A+ 2.3.3.4
resolve(x);
}
} catch (e) {
// Promise/A+ 2.3.3.2
if (called) return;
called = true;
reject(e)
}
} else {
// 如果 x 是個普通值就直接返回 resolve 作為結(jié)果 Promise/A+ 2.3.4
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks= [];
let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
//解決 onFufilled,onRejected 沒有傳值的問題
//Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
//因為錯誤的值要讓后面訪問到,所以這里也要跑出個錯誤,不然會在之后 then 的 resolve 中捕獲
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 每次調(diào)用 then 都返回一個新的 promise Promise/A+ 2.2.7
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
//Promise/A+ 2.2.2
//Promise/A+ 2.2.4 --- setTimeout
setTimeout(() => {
try {
//Promise/A+ 2.2.7.1
let x = onFulfilled(this.value);
// x可能是一個proimise
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//Promise/A+ 2.2.7.2
reject(e)
}
}, 0);
}
if (this.status === REJECTED) {
//Promise/A+ 2.2.3
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0);
}
if (this.status === 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;
}
}
Promise.all
核心就是有一個失敗則失敗,全成功才進行 resolve
Promise.all = function(values) {
if (!Array.isArray(values)) {
const type = typeof values;
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}
return new Promise((resolve, reject) => {
let resultArr = [];
let orderIndex = 0;
const processResultByKey = (value, index) => {
resultArr[index] = value;
if (++orderIndex === values.length) {
resolve(resultArr)
}
}
for (let i = 0; i < values.length; i++) {
let value = values[i];
if (value && typeof value.then === 'function') {
value.then((value) => {
processResultByKey(value, i);
}, reject);
} else {
processResultByKey(value, i);
}
}
});
}
到此這篇關(guān)于JavaScript實現(xiàn)手寫promise的示例代碼的文章就介紹到這了,更多相關(guān)JavaScript手寫promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js編寫當(dāng)天簡單日歷效果【實現(xiàn)代碼】
下面小編就為大家?guī)硪黄猨s編寫當(dāng)天簡單日歷效果【實現(xiàn)代碼】。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考2016-05-05
微信小程序 動態(tài)綁定數(shù)據(jù)及動態(tài)事件處理
這篇文章主要介紹了微信小程序 動態(tài)綁定數(shù)據(jù)及動態(tài)事件處理的相關(guān)資料,需要的朋友可以參考下2017-03-03
js打造數(shù)組轉(zhuǎn)json函數(shù)
這里給大家分享的是一段使用js實現(xiàn)數(shù)組轉(zhuǎn)換成json的函數(shù)代碼,代碼簡潔易懂,并附上了使用方法,小伙伴們拿去試試。2015-01-01
uniapp使用u-upload組件來實現(xiàn)圖片上傳功能
最近在用uniapp開發(fā)微信小程序,下面這篇文章主要給大家介紹了關(guān)于uniapp使用u-upload組件來實現(xiàn)圖片上傳功能的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-01-01

