JavaScript實(shí)現(xiàn)手寫promise的示例代碼
背景
promise 作為前端開發(fā)中常用的函數(shù),解決了 js 處理異步時(shí)回調(diào)地獄的問題,大家應(yīng)該也不陌生了,今天來學(xué)習(xí)一下 promise 的實(shí)現(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ǎn),也就是我們本次要做的事情:
- 在調(diào)用 Promise 時(shí),會返回一個(gè) Promise 對象,包含了一些熟悉和方法(all/resolve/reject/then…)
- 構(gòu)建 Promise 對象時(shí),需要傳入一個(gè) executor 函數(shù),接受兩個(gè)參數(shù),分別是resolve和reject,Promise 的主要業(yè)務(wù)流程都在 executor 函數(shù)中執(zhí)行。
- 如果運(yùn)行在 excutor 函數(shù)中的業(yè)務(wù)執(zhí)行成功了,會調(diào)用 resolve 函數(shù);如果執(zhí)行失敗了,則調(diào)用 reject 函數(shù)。
- promise 有三個(gè)狀態(tài):pending,fulfilled,rejected,默認(rèn)是 pending。只能從pending到rejected, 或者從pending到fulfilled,狀態(tài)一旦確認(rèn),就不會再改變;
- promise 有一個(gè)then方法,接收兩個(gè)參數(shù),分別是成功的回調(diào) onFulfilled, 和失敗的回調(diào) onRejected。
// 三個(gè)狀態(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 時(shí)才可以更新狀態(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 時(shí)才可以更新狀態(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ā)生異常時(shí)執(zhí)行失敗邏輯 reject(error); } } // 包含一個(gè) then 方法,并接收兩個(gè)參數(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) } )
這個(gè)時(shí)候我們很開心,但是別開心的太早,promise 是為了處理異步任務(wù)的,我們來試試異步任務(wù)好不好使:
const promise = new Promise((resolve, reject) => { // 傳入一個(gè)異步操作 setTimeout(() => { resolve('成功'); },1000); }).then( (data) => { console.log('success', data) }, (err) => { console.log('faild', err) } )
發(fā)現(xiàn)沒有任何輸出,我們來分析一下為什么:
new Promise 執(zhí)行的時(shí)候,這時(shí)候異步任務(wù)開始了,接下來直接執(zhí)行 .then 函數(shù),then函數(shù)里的狀態(tài)目前還是 padding,所以就什么也沒執(zhí)行。
那么我們想想應(yīng)該怎么改呢?
我們想要做的就是,當(dāng)異步函數(shù)執(zhí)行完后,也就是觸發(fā) resolve 的時(shí)候,再去觸發(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); }) } } }
這下再進(jìn)行測試:
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)用
這里就不分析細(xì)節(jié)了,大體思路就是每次 .then 的時(shí)候重新創(chuàng)建一個(gè) promise 對象并返回 promise,這樣下一個(gè) then 就能拿到前一個(gè) then 返回的 promise 了。
const PENDING = 'PENDING'; const FULFILLED = 'FULFILLED'; const REJECTED = 'REJECTED'; const resolvePromise = (promise2, x, resolve, reject) => { // 自己等待自己完成是錯(cuò)誤的實(shí)現(xiàn),用一個(gè)類型錯(cuò)誤,結(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 同時(shí)調(diào)用的時(shí)候) Promise/A+ 2.3.3.1 let then = x.then; if (typeof then === 'function') { // 不要寫成 x.then,直接 then.call 就可以了 因?yàn)?x.then 會再次取值,Object.defineProperty Promise/A+ 2.3.3.3 then.call(x, y => { // 根據(jù) promise 的狀態(tài)決定是成功還是失敗 if (called) return; called = true; // 遞歸解析的過程(因?yàn)榭赡?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 是個(gè)普通值就直接返回 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 是個(gè)普通值就直接返回 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; //因?yàn)殄e(cuò)誤的值要讓后面訪問到,所以這里也要跑出個(gè)錯(cuò)誤,不然會在之后 then 的 resolve 中捕獲 onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; // 每次調(diào)用 then 都返回一個(gè)新的 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可能是一個(gè)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
核心就是有一個(gè)失敗則失敗,全成功才進(jìn)行 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實(shí)現(xiàn)手寫promise的示例代碼的文章就介紹到這了,更多相關(guān)JavaScript手寫promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 輪播圖和自定義滾動條配合鼠標(biāo)滾輪分享代碼貼
本文給大家分享一段js輪播圖和自定義滾動條的代碼片段,布局和樣式小編沒給大家多介紹,大家可以根據(jù)個(gè)人需求優(yōu)化,具體實(shí)現(xiàn)代碼,大家可以參考下面代碼片段2016-10-10ES6新特性之解構(gòu)、參數(shù)、模塊和記號用法示例
這篇文章主要介紹了ES6新特性之解構(gòu)、參數(shù)、模塊和記號用法,結(jié)合實(shí)例形式分析了解構(gòu)、參數(shù)、模塊和記號的功能、用法及相關(guān)使用注意事項(xiàng),需要的朋友可以參考下2017-04-04詳述JavaScript實(shí)現(xiàn)繼承的幾種方式(推薦)
這篇文章主要介紹了詳述JavaScript實(shí)現(xiàn)繼承的幾種方式(推薦)的相關(guān)資料,需要的朋友可以參考下2016-03-03