手写promise
带大家手写一个 promis。在手写之前我会先简单介绍一下为什么要使用promise、promise的基本用法和使用场景。因为我们得了解清楚需求才能更好地实现它。
本文采取一贯保姆式风格,喂到诸君嘴边了。来,大郎,吃药了~
promise 简介
Promise 的出现解决了我们之前处理异步编程使用回调函数,层层嵌套的地狱回调问题,地狱回调易读性差逻辑不清晰,相信谁接手了一个这样的代码都要骂一句上写得什么玩意儿。当然了处理异步编程还有 generator 和 async 和 await等方式,async 和 await 相比于promise又有一些优点。我在最后会简单的介绍一下,但是不会做过多展开。
地狱回调
我们看下这段代码,是不是看过去就不舒服,然而实际写业务代码时只会比这个更复杂,比如发送一个ajax请求1,请求1里有大量的业务处理代码,然后再发送请求2,并且请求2依赖于请求1结果······
let i = 0
setTimeout(() => {
let i = 1
console.log(i)
setTimeout((i) => {
let j = i + 2
console.log(j)
setTimeout(j => {
let k = j + 3
console.log(k)
}, 1000, j);
}, 1000, i);
}, 1000);
promise 解决了这个问题
上面这段代码用 promise 实现,这样看好像感觉代码量更多了,但是明显代码逻辑更加清晰了,而且嵌套越多,promise 的优势越明显。
let i = 0
let p = new Promise((resolve, reject) => {
setTimeout(() => {
i = i + 1
console.log(i)
resolve(1)
}, 1000)
}).then(data => {
return new Promise((resolve, reject) => {
setTimeout(() => {
i = i + 2
console.log(i)
resolve(i)
}, 1000);
})
}).then(data => {
setTimeout(() => {
i = i + 3
console.log(i)
}, 1000);
})
promise 的基本特性及用法
基本特性:(记住这几个特性,这就是我们实现手写 promise 的具体需求,现在不理解也正常,在后续实现过程中就会慢慢理解了)
- promise 有三种状态,pending等待、fulfilled 完成、rejected 失败
- pending 可以转为 fulfilled,也可转为 rejected,一旦状态转变了,状态不可再更改
- resolve 为成功,状态由 pending 转为 fulfilled,并且接收参数 value
- reject 为失败,状态由 pending 转为 fulfilled,并且接收参数 reason
- Then 方法接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected
// index.js
new Promise((resolve, reject) => {
setTimeout(() => {
// 异步操作成功时
resolve('hello')
// 异步操作失败时
// reject('失败')
}, 2000)
}).then(data => {
console.log(data)
}, reason => {
console.log(reason)
})
Promise 实现
第一步 实现一个最基本的 promise
// index.js ----- 栗子1
let Promise = require('./promise')
let p = new Promise((resolve, reject) => {
resolve('成功啦~')
// reject('失败啦~')
})
p.then(data => {
console.log(data)
})
由上面这段代码我们和promise的特性,我们理一下需求:
- promise 要偿还一个和回调方法 executor,作为Promise 的参数
- Executor接收resolve 和 reject 作为 executor 的参数,resolve('成功啦'),接收value参数,reject('失败啦~') 接收 reason 参数。
- 结合promise 的规定,promise 有三种状态,pending、fulfilled、rejected
- 实现一个 then 方法,接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected
代码实现
-
promise 要偿还一个和回调方法 executor,作为Promise 的参数
// promise.js class Promise { constructor(executor) { excutor() } } module.exports = Promise
-
Executor接收resolve 和 reject 作为 executor 的参数,resolve('成功啦'),接收value参数,reject('失败啦~') 接收 reason 参数。
// promise.js class Promise { constructor(executor) { this.value = undefined this.reason = undefined const resolve = (value) => { this.value = value } const reject = (reason) => { this.reason = reason } excutor(resolve, reject) } } module.exports = Promise
-
结合promise 的规定,promise 有三种状态,pending、fulfilled、rejected
// promise.js class Promise { constructor(executor) { // 定义一个状态,初始是 pending this.state = 'pending' this.value = undefined this.reason = undefined const resolve = (value) => { if (this.state === 'pending') { // 成功,状态由 pending 变为 fulfilled this.state = 'fulfilled' // 接收 成功啦~ this.value = value } } const reject = (reason) => { if(this.state === 'pendinng') { // 失败,状态由 pending 变为 rejected this.state = 'rejected' // 接收 失败啦~ this.reason = reason } } excutor(resolve, reject) } } module.exports = Promise
-
实现一个 then 方法,接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected
class Promise { constructor(executor) { this.state = 'pending' this.value = undefined this.reason = undefined const resolve = (value) => { if (this.state === 'pending') { this.value = value this.state = 'fulfilled' } } const reject = (reason) => { if (this.state === 'pending') { this.reason = reason this.state = 'rejected' } } executor(resolve, reject) } // 实现 then 方法 then(onFulfilled, onRejected) { // 成功 if (this.state === 'fulfilled') { // 接收成功传来的数据 onFulfilled(this.value) } // 失败 if (this.state === 'rejected') { // 接收失败传来的数据 onRejected(this.reason) } } } module.exports = Promise
运行一下
栗子1
成功打印出
第二步 实现异步功能 发布订阅模式
很显然 栗子1
是一个同步操作,我们来看下 栗子2
,发现在 then 里的 console.log 啥也没打印出来。
// index.js ------ 栗子2
let Promise = require('./promise')
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('延迟1s 成功啦~')
}, 1000)
})
p.then(data => {
console.log(data)
})
- 这是为什呢?
因为到目前为止,我们实现的 promise 都是同步的,当我们执行 executor 时先把同步操作执行完成,发现有一个异步操作 settimeout,先让她去排队了(这里需要了解一下事件循环机制),然后立刻去同步执行了 then 方法。
也就是说 settimeout 里的 resolve 方法根本没执行,状态也就还是 pending,value 也没有获取到我们传入的 延迟一秒 成功啦~
,所以即使执行了 then 方法也没用,因为状态还是 pending。
- 怎么解决呢?
既然 then 自己无法知道 resolve 什么时候执行,是否执行了,那resolve执行完后就需要有个东西告诉then,执行完了。
铛铛~ 发布订阅者模式
,订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
举个栗子,比如我们去吃海底捞,海底捞前台有一个做手膜的福利,我也不知道前台小姐姐还有多少个手膜要做,什么时候轮到我,我就去他公众号上预约,排了一个号,就继续吃我的海底捞了,然后前台小姐姐做完了就通过我在公众号上留的信息,排的号打电话给我叫我我去做手膜。
我们理一下需求:
还在 pending 的时候我们要记录下,哪些是要等到resolve完成后执行的,我们把这些放进一个数组中。当 resolve 或是 reject 后再把它们拿出来执行。
class Promise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
// 定义数组,存放稍后要完成的任务
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === 'pending') {
this.value = value
this.state = 'fulfilled'
/* 成功了,在这个例子中,相当于过了 1秒了开始执行resolve了,
状态改变后,把我们预约好的任务拿出来依次执行 */
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.reason = reason
this.state = 'rejected'
this.onRejectedCallbacks.forEach(fn => fn())
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
if(this.state === 'pending') {
/* 因为异步导致 state 还在 pending 状态
所以把 要做的任务先放到预约的数组队列里
*/
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
module.exports = Promise
第三步 链式调用
promise 的一个优势就是链式调用,逻辑清晰,我们能经常会这样链式写,栗子3
// index.js ------ 栗子3
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('延迟1秒 成功啦~')
}, 1000)
})
p.then(data => {
let str = data + '我再第一个 then 里哦!'
console.log(str)
return str
/* return new Promise((resolve, reject) => {
resolve('分割符')
}
*/
})
}).then(data => {
let str = data + '我在第二个 then 里哦!'
console.log(str)
})
需求:
-
promise本身就包含 then 方法,要实现链式调用我们直接在 then 里 返回 一个 promise 不就行了吗?把then方法里所有任务都放到promise
promise((resolve, reject) => {原来then里的任务})
然后再接下一个 then。class Promise { constructor(executor) { this.state = 'pending' this.value = undefined this.reason = undefined this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { if (this.state === 'pending') { this.value = value this.state = 'fulfilled' this.onResolvedCallbacks.forEach(fn => fn()) } } const reject = (reason) => { if (this.state === 'pending') { this.reason = reason this.state = 'rejected' this.onRejectedCallbacks.forEach(fn => fn()) } } executor(resolve, reject) } then(onFulfilled, onRejected) { // 为了打到链式调用 then , 我们返回一个新的 promise 实例 const promise2 = new Promise((resolve, reject) => { if (this.state === 'fulfilled') { /* 这里的 onFulfilled 的结果不就是栗子3中回调函数的结果吗,然后接着立刻执行 下一个then方法*/ resolve(onFulfilled(this.value)) } if (this.state === 'rejected') { reject(onRejected(this.reason)) } if(this.state === 'pending') { this.onResolvedCallbacks.push(() => { resolve(onFulfilled(this.value)) }) this.onRejectedCallbacks.push(() => { reject(onRejected(this.reason)) }) } }) return promise2 } } module.exports = Promise
-
Then 方法里可能 返回的是个普通的值,也可能返回的是一个 promise。如果是 普通值,我们直接传到 value 里就行了,如果是个 promise 我们还要执行下 promise的 resolve 或是 reject。
// index.js --- 栗子4 then 里可能返回普通值,也可能返回 promise,这里我们需要处理一下 let Promise = require('./promise') let p = new Promise((resolve, reject) => { setTimeout(() => { resolve('延迟1秒 成功啦~') }, 1000) }) p.then(data => { let str = data + '我再第一个 then 里哦' console.log(str) return new Promise((resolve, reject) => { resolve(str) }) }).then(data => { let str = data + '我在第二个 then 里哦' console.log(str) })
// promise.js class Promise { constructor(executor) { this.state = 'pending' this.value = undefined this.reason = undefined this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { if (this.state === 'pending') { this.value = value this.state = 'fulfilled' this.onResolvedCallbacks.forEach(fn => fn()) } } const reject = (reason) => { if (this.state === 'pending') { this.reason = reason this.state = 'rejected' this.onRejectedCallbacks.forEach(fn => fn()) } } executor(resolve, reject) } then(onFulfilled, onRejected) { const promise2 = new Promise((resolve, reject) => { if (this.state === 'fulfilled') { /* 因为此时 promise2 还没有生成,所以我们利用 settimeout, 把里面的内容放到下一个事件循环中执行,那时promise2 已经在这个循环里生成了 */ setTimeout(() => { let x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) }, 0) } if (this.state === 'rejected') { setTimeout(() => { let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) }, 0) } if(this.state === 'pending') { this.onResolvedCallbacks.push(() => { setTimeout(() => { let x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) }, 1000) }) this.onRejectedCallbacks.push(() => { setTimeout(() => { let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) }, 0) }) } }) return promise2 } } function resolvePromise(promise, x, resolve, reject) { if(typeof x === 'function' || (typeof x === 'object' && x !== null)) { try { const then = x.then if (typeof then === 'function') { // 判断是一个 x 是一个promise,就执行 then 方法里的两个回调,可能是成功,也可能失败 then.call(x, y => { resolve(y) }, r => { reject(r) }) } } catch (err) { reject(err) } } else { // 只是一个普通的值 resolve(x) } } module.exports = Promise
我们执行一下
-
如果我们返回的promise里还有promise咋办?递归啊!
// index.js ----- 栗子5 let Promise = require('./promise') let p = new Promise((resolve, reject) => { setTimeout(() => { resolve('延迟1秒 成功啦~') }, 1000) }) p.then(data => { let str = data + '我再第一个 then 里哦' console.log(str) // 返回值是 promise 包 promise return new Promise((resolve, reject) => { resolve(str + new Promise((resolve, reject) => { resolve(data + 'promise 中的 promise') console.log(str + 'promise 中的 promise') })) }) }).then(data => { let str = data + '我在第二个 then 里哦' console.log(str) })
// promise.js .... function resolvePromise(promise, x, resolve, reject) { if(typeof x === 'function' || (typeof x === 'object' && x !== null)) { try { const then = x.then if (typeof then === 'function') { then.call(x, y => { // 递归 resolvePromise(promise, y, resolve, reject) }, r => { reject(r) }) } } catch (err) { reject(err) } } else { resolve(x) } } ....
我觉得这样下来就差不多了,promise 当然还有 all 等方法,下次有时间再更,今天乏了,不想写了。
参考文档: