JavaScript面試必備技巧之手寫一個Promise
很多同學(xué)在面試的時候都會被要求手寫一個Promise,那么今天我總結(jié)了一些手寫Promise的方法,可以跟著我的思路一起來實現(xiàn)一個Promise,讓我們的面試更有把握。同時我們也會實現(xiàn)一下Promsie常見的方法比如:all、race、allSettled、any。
基本實現(xiàn)
首先我們可以用類來實現(xiàn)Promise,而且Promise有三種狀態(tài):pending、fulfilled、rejected。初始狀態(tài)為pending。還需要對Promise的終值進(jìn)行初始化。Promise還有兩個方法resolve和reject。
Promise有四個特點:
- 執(zhí)行了resolve,Promise狀態(tài)就會變成fulfilled
- 執(zhí)行了reject,Promise狀態(tài)就會變成rejected
- Promise狀態(tài)不可逆,第一次成功就永久為fulfilled,第一次失敗就永久為rejected
- Promise中有throw的話,就相當(dāng)于執(zhí)行了rejected
下面我就來簡單的實現(xiàn)一下吧
實現(xiàn)resolve和reject
class MyPromise { constructor(executor) { // 初始化值 this.initValue() // 初始化this指向 this.initBind() try { executor(this.resolve, this.reject) } catch (error) { this.reject(error) } } initValue() { // 初始化值 this.promiseResult = null this.promiseState = "pending" // 初始狀態(tài) } initBind() { this.resolve = this.resolve.bind(this) this.reject = this.reject.bind(this) } resolve(val) { this.promiseState = "fulfilled" this.promiseResult = val } reject(reason) { this.promiseState = "rejected" this.promiseResult = reason } }
測試一下吧
const test1 = new MyPromise((resolve, reject) => { resolve("success") }) console.log(test1) // MyPromise{ promiseResult: 'success', promiseState: 'fulfilled' } const test2 = new MyPromise((resolve, reject) => { reject("fail") }) console.log(test2) // MyPromise{ promiseResult: 'fail', promiseState: 'rejected' } const test3 = new MyPromise((resolve, reject) => { throw "fail" }) console.log(test3) // MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }
這里重點說一下initBind中為什么要給resolve和reject綁定this。我們可以看到在resolve和reject中使用了this.promiseState
和this.promiseResult
。我們需要把它的this綁定到實例才對。
狀態(tài)不可變
如果我們執(zhí)行下面的代碼
const test = new MyPromise((resolve, reject) => { resolve("success") reject("fail") }) console.log(test) // MyPromise{ promiseResult: 'fail', promiseState: 'rejected' }
這就不符合我們的預(yù)期了,因為我們需要的是狀態(tài)不可變。所以我們將代碼改造一下,這里只需要修改resolve和reject就可以了
resolve(val) { if (this.promiseState !== "pending") return this.promiseState = "fulfilled" this.promiseResult = val } reject(reason) { if (this.promiseState !== "pending") return this.promiseState = "rejected" this.promiseResult = reason }
當(dāng)執(zhí)行resolve或reject的時候,發(fā)現(xiàn)狀態(tài)不是pending就說明狀態(tài)已經(jīng)改變了,直接return即可。
then
我們首先看下原始的Promise的then實現(xiàn)的效果
// 直接輸出success const p1 = new Promise((resolve, reject) => { resolve("success") }).then( (res) => console.log(res), (err) => console.log(err) ) // 1s后輸出fail const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("fail") }, 1000) }).then( (res) => console.log(res), (err) => console.log(err) ) // 鏈?zhǔn)秸{(diào)用 直接輸出2000 const p3 = new Promise((resolve, reject) => { resolve(1000) }) .then( (res) => 2 * res, (err) => console.log(err) ) .then( (res) => console.log(res), (err) => console.log(err) )
由此可以得出結(jié)論:
- then 接受兩個回調(diào),一個是成功回調(diào), 一個是失敗的回調(diào)
- 當(dāng)Promise為fulfilled執(zhí)行成功的回調(diào),為rejected 執(zhí)行失敗的回調(diào)
- 如果resolve或者reject在定時器里面執(zhí)行,則定時器結(jié)束后再執(zhí)行then
- then 支持鏈?zhǔn)秸{(diào)用,下一次then執(zhí)行受上一次then返回值的影響
then實現(xiàn)
由結(jié)論1和2可以實現(xiàn)
then(onFulfilled, onRejected) { // 首先要校驗兩個回調(diào)是否是函數(shù) onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } if (this.promiseState === "fulfilled") { onFulfilled(this.promiseResult) } else if (this.promiseState === "rejected") { onRejected(this.promiseResult) } }
但是我們?nèi)绾伪WC回調(diào)是在定時器結(jié)束后執(zhí)行呢?首先在定時器結(jié)束之前Promise的狀態(tài)一直是pending的,回調(diào)結(jié)束之后我們才能知道是fulfilled或者是rejected,所以在執(zhí)行then的時候,如果promiseState是pending我們就把回調(diào)收集起來,當(dāng)回調(diào)結(jié)束之后再觸發(fā)。那使用什么來保存這些回調(diào)的呢?這里建議使用數(shù)組,因為一個Promise實例可能會多次執(zhí)行then,用數(shù)組一個個保存就可以了
initValue() { // 初始化值 this.promiseResult = null this.promiseState = "pending" // 初始狀態(tài) this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] } resolve(val) { if (this.promiseState !== "pending") return this.promiseState = "fulfilled" this.promiseResult = val while (this.onFulfilledCallbacks.length) { this.onFulfilledCallbacks.shift()(this.promiseResult) } } reject(reason) { if (this.promiseState !== "pending") return this.promiseState = "rejected" this.promiseResult = reason while (this.onRejectedCallbacks.length) { this.onRejectedCallbacks.shift()(this.promiseResult) } } then(onFulfilled, onRejected) { // 首先要校驗兩個回調(diào)是否是函數(shù) onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } if (this.promiseState === "fulfilled") { onFulfilled(this.promiseResult) } else if (this.promiseState === "rejected") { onRejected(this.promiseResult) } else if (this.promiseState === "pending") { this.onFulfilledCallbacks.push(onFulfilled.bind(this)) this.onRejectedCallbacks.push(onRejected.bind(this)) } }
鏈?zhǔn)秸{(diào)用
我們再來重新看下鏈?zhǔn)秸{(diào)用的例子
// 鏈?zhǔn)秸{(diào)用 直接輸出2000 const p3 = new Promise((resolve, reject) => { resolve(1000) }) .then( (res) => 2 * res, (err) => console.log(err) ) .then( (res) => console.log(res), (err) => console.log(err) ) // 鏈?zhǔn)秸{(diào)用 輸出3000 const p4 = new Promise((resolve, reject) => { resolve(1000) }) .then( (res) => new Promise((resolve, reject) => resolve(3 * res)), (err) => console.log(err) ) .then( (res) => console.log(res), (err) => console.log(err) )
由此可得:
- then方法本身會返回一個新的Promise對象
- 如果返回值是promise對象,返回值為成功,新promise就是成功
- 如果返回值是promise對象,返回值為失敗,新promise就是失敗
- 如果返回值是非promise對象,新promise對象就是成功,值為此返回值
這里我們對then改造了一下
then(onFulfilled, onRejected) { // 首先要校驗兩個回調(diào)是否是函數(shù) onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } var thenPromise = new MyPromise((resolve, reject) => { const resolvePromise = (cb) => { try { const x = cb(this.promiseResult) if (x === thenPromise && x) { throw new Error("不能返回自身") } if (x instanceof MyPromise) { // 如果是promise 返回值為成功 新promise 就是成功 // 如果是promise 返回值失敗 新promise 就是失敗 x.then(resolve, reject) } else { // 如果不是promise 直接返回成功 resolve(x) } } catch (error) { reject(error) throw new Error(error) } } if (this.promiseState === "fulfilled") { resolvePromise(onFulfilled) } else if (this.promiseState === "rejected") { resolvePromise(onRejected) } else if (this.promiseState === "pending") { this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled)) this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected)) } }) return thenPromise }
then會返回一個新的Promise,在這個新的promise中定義了方法resolvePromise ,接收一個cb回調(diào)函數(shù),這個cb就是傳入的onFulfilled或者onRejected。如果這兩個回調(diào)返回的不是promise,那結(jié)果直接resolve出去。如果是promise,那么就需要根據(jù)它的狀態(tài)來決定下一步操作。那它到底是成功還是失敗的呢,只有它的then知道。然后把resolve和reject當(dāng)做回調(diào)傳給then,如果x返回的是成功的Promise,則會執(zhí)行resolve, 如果x返回的是失敗的promise,則會執(zhí)行reject。這樣鏈?zhǔn)秸{(diào)用的then才會知道執(zhí)行哪一個回調(diào)。
執(zhí)行順序
const p = new Promise((resolve, reject) => { resolve(1) }).then( (res) => console.log(res), (err) => console.log(err) ) console.log(2)
為了實現(xiàn)類似的功能,使用setTimeout代替
看下完整代碼
class MyPromise { constructor(executor) { // 初始化值 this.initValue() // 初始化this指向 this.initBind() try { executor(this.resolve, this.reject) } catch (error) { this.reject(error) } } initValue() { // 初始化值 this.promiseResult = null this.promiseState = "pending" // 初始狀態(tài) this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] } initBind() { this.resolve = this.resolve.bind(this) this.reject = this.reject.bind(this) } resolve(val) { if (this.promiseState !== "pending") return this.promiseState = "fulfilled" this.promiseResult = val while (this.onFulfilledCallbacks.length) { this.onFulfilledCallbacks.shift()(this.promiseResult) } } reject(reason) { if (this.promiseState !== "pending") return this.promiseState = "rejected" this.promiseResult = reason while (this.onRejectedCallbacks.length) { this.onRejectedCallbacks.shift()(this.promiseResult) } } then(onFulfilled, onRejected) { // 首先要校驗兩個回調(diào)是否是函數(shù) onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason } var thenPromise = new MyPromise((resolve, reject) => { const resolvePromise = (cb) => { setTimeout(() => { try { const x = cb(this.promiseResult) if (x === thenPromise && x) { throw new Error("不能返回自身") } if (x instanceof MyPromise) { // 如果是promise 返回值為成功 新promise 就是成功 // 如果是promise 返回值失敗 新promise 就是失敗 x.then(resolve, reject) } else { // 如果不是promise 直接返回成功 resolve(x) } } catch (error) { reject(error) throw new Error(error) } }) } if (this.promiseState === "fulfilled") { resolvePromise(onFulfilled) } else if (this.promiseState === "rejected") { resolvePromise(onRejected) } else if (this.promiseState === "pending") { this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled)) this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected)) } }) return thenPromise } }
其他方法
all
- 接收一個Promise數(shù)組,數(shù)組中如有非Promise項,則此項當(dāng)做成功
- 如果所有Promise都成功,則返回成功結(jié)果數(shù)組
- 如果有一個Promise失敗,則返回這個失敗的結(jié)果
static all(promiseList) { const result = [] const count = 0 return new MyPromise((resolve, reject) => { const addData = function (index, value) { result[index] = value count++ if (count === promiseList.length) resolve(result) } promiseList.forEach((promise, index) => { if (promise instanceof MyPromise) { promise.then( (res) => { addData(index, res) }, (err) => { reject(err) } ) } else { addData(index, promise) } }) }) }
race
- 接收一個Promise數(shù)組,數(shù)組中有非Promise項,則此項當(dāng)做成功
- 哪個Promise最快得到結(jié)果,就返回那個結(jié)果,無論成功失敗
race(promiseList) { return new MyPromise((resolve, reject) => { promiseList.forEach((promise) => { if (promise instanceof MyPromise) { promise.then( (res) => resolve(res), (err) => reject(err) ) } else { resolve(promise) } }) }) }
allSettled
- 接收一個Promise數(shù)組,數(shù)組中有非Promise項,則此項當(dāng)做成功
- 把每個Promise的結(jié)果,集合成數(shù)組后返回
allSettled(promiseList) { const result = [] let count = 0 return new MyPromise((resolve, reject) => { const addData = function (status, value, i) { result[i] = { status, value } count++ if (count === promiseList.length) { resolve(result) } } promiseList.forEach((promise, index) => { if (promise instanceof MyPromise) { promise.then( (res) => { addData("fulfilled", res, index) }, (err) => { addData("reject", err, index) } ) } else { resolve(promise) } }) }) }
any
與all相反
- 接收一個Promise數(shù)組,數(shù)組中如有非Promise項,則此項當(dāng)做成功
- 如果有一個Promise成功,則返回這個成功結(jié)果
- 如果所有Promise都失敗,則報錯
static any(promiseList) { return new MyPromise((resolve, reject) => { let count = 0 promiseList.forEach((promise) => { if (promise instanceof MyPromise) { promise.then( (res) => { resolve(res) }, (err) => { count++ if (count === promiseList.length) { reject("error") } } ) } else { resolve(promise) } }) }) }
到此這篇關(guān)于JavaScript面試必備技巧之手寫一個Promise的文章就介紹到這了,更多相關(guān)JavaScript手寫Promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序多列表渲染數(shù)據(jù)開關(guān)互不影響的實現(xiàn)
這篇文章主要介紹了微信小程序多列表渲染數(shù)據(jù)開關(guān)互不影響的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06js實現(xiàn)動態(tài)添加、刪除行、onkeyup表格求和示例
動態(tài)添加、刪除行想必大家并不陌生,下面為大家介紹通過js是如何實現(xiàn)的,有此需求的朋友可不要錯過了哈2013-08-08javascript 模擬JQuery的Ready方法實現(xiàn)并出現(xiàn)的問題
今天在閱讀網(wǎng)上一些模擬Jq的ready方法時,發(fā)現(xiàn)一些小細(xì)節(jié),就是網(wǎng)上的ready事件大部分都是在onload事件執(zhí)行后加載,而jquery確能在onload加載前。2009-12-12JavaScript實現(xiàn)兼容IE6的收起折疊與展開效果實例
這篇文章主要介紹了JavaScript實現(xiàn)兼容IE6的收起折疊與展開效果,結(jié)合具體實例形式分析了javascript事件響應(yīng)及針對頁面元素屬性的動態(tài)操作相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-09-09Bootstrap每天必學(xué)之彈出框(Popover)插件
Bootstrap每天必學(xué)之彈出框(Popover)插件,彈出框的內(nèi)容完全可使用 Bootstrap 數(shù)據(jù) API(Bootstrap Data API)來填充,如何實現(xiàn)請參考本文2016-04-04