本篇文章是《包教包会,和你实现一个Promise》的第二篇,紧接着第一篇的内容。因此,若是你尚未看过第一篇,你可能须要看一下包教包会,和你实现一个Promise(一)。javascript
第一篇咱们已经实现了一个形式上很是相似Promise的MyPromise,可是尽管使用形式有点像,它离彻底符合Promise A+规范的Promise还差的远。如今的MyPromise:java
function MyPromise(executor) {
this.status = 'pending'
this.data = undefined
this.reason = undefined
this.resolvedCallbacks = []
this.rejectedCallbacks = []
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.data = value
this.resolvedCallbacks.forEach(fn => fn(this.data))
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectedCallbacks.forEach(fn => fn(this.reason))
}
}
executor(resolve, reject)
}
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
this.resolvedCallbacks.push(onResolved)
this.rejectedCallbacks.push(onRejected)
}
if (this.status === 'fulfilled') {
onResolved(this.data)
}
if (this.status === 'rejected') {
onRejected(this.reason)
}
}
复制代码
如今这个MyPromise的问题在于,它没法进行链式调用。咱们在使用Promise的时候,会有这样的代码:promise
let promise1 = new Promise(function(resolve, reject) {
// 模拟异步
setTimeout(() => {
let flag = Math.random() > 0.5 ? true : false
if (flag) {
resolve('success')
} else {
reject('fail')
}
}, 1000)
})
promise1.then(res => {
return 1
}, err => {
return err
}).then(res => {
console.log(res)
})
复制代码
可是目前MyPromise的then是一次性,执行完了就完了,没有返回能then的东西。浏览器
根据Promise A+规范,一个Promise实例在then以后必定会返回一个新的Promise实例,这样就可使用then来实现链式调用了。app
在实现then方法以前,咱们先简单聊一下实现链式调用的技巧。通常来讲,实现链式调用有两个方法:dom
function $(selector) {
return new jQuery(selector)
}
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice
this.domArray = slice.call(document.querySelectorAll(selector))
// ...
}
append() {
// ...
return this
}
addClass() {
//..
return this
}
}
复制代码
上面代码思路:先构造一个jQuery对象,它的某些方法在执行完了以后返回this,也就是这个一开始构造的jQuery对象。这样,就能够这样链式调用下去:$('.app').append($span).addClass('test')
异步
class Promise {
constructor() {
//...
}
then() {
//...
let promise2 = new Promise() // 重点在这
return promise2
}
}
// 使用
let promise1 = new Promise()
let promise2 = promise1.then()
let promise3 = promise2.then()
// 也能够直接写成这样
promise1.then().then()
复制代码
这样,每次then方法执行的时候,都会返回一个彻底新的Promise实例,就能够继续往下then了。函数
到这里,咱们终于来到了Promise最核心部分,那就是then方法。在整个Promise A+规范里,大部份内容都在说then是如何实现的,说then方法是Promise的核心一点问题都没有。post
咱们先明确then方法的功能:测试
接下来分开解释。
第一点,实现链式调用的方法,上面讲过了,再也不赘述。
第二点,then方法返回的新Promise实例状态依赖于当前实例状态和回调返回值的状态。要理解这句话,请仔细看下面的例子:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
let promise2 = promise1.then(res => {
return res
})
console.log(promise1) // pendding状态
console.log(promise2) // pending状态
setTimeout(() => {
console.log(promise1) // 成功状态
console.log(promise2) // 成功状态
}, 1000)
复制代码
从上面的代码能够看出:若是promise1是pending状态,那promise2也必定是pending状态。只有当promise1状态肯定,promise2的状态才有可能肯定。 那当promise1状态肯定的时候,是否是promise2的状态就肯定了呢?也不是,还要看promise1的then里注册的两个函数的返回值。 请看下面的例子:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
let promise2 = promise1.then(res => {
return Promise.reject('拒绝') // 注意这里,这个回调返回了一个拒绝状态Promise哦
})
console.log(promise1) // pending状态
console.log(promise2) // pending状态
setTimeout(() => {
console.log(promise1) // 成功状态
console.log(promise2) // 这里是失败状态
}, 1000)
复制代码
从上面的两个例子,咱们能够获得如下结论:当咱们在then方法里构造新的Promise时,咱们不只要根据当前Promise实例的状态使用不一样的策略,同时还要考虑当前then方法传递的两个回调的结果。
第三点,其实很好理解,当promise1的then方法里传递的回调执行的结果,能够被下个实例拿到,这里很简单,得到回调的结果,再根据条件resolve或者reject传进去便可。
以上的内容,若是听不懂不要紧,下面讲then方法具体实现的时候还会再说道。
then方法须要返回一个新的Promise实例,并且须要根据当前实例的状态来构造这个新的实例。因此MyPromise的then方法的代码改为以下的样子:
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
})
// this.resolvedCallbacks.push(onResolved)
// this.rejectedCallbacks.push(onRejected)
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
})
// onResolved(this.data)
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
})
// onRejected(this.reason)
}
return promise2
}
复制代码
声明一个须要返回的promise2,而后根据当前当前实例的状态来给它赋值。在这里,咱们先注释了每一个if判断里原来的代码。接下来的重点就是构造promise2,看它的executor函数具体如何实现了。 咱们先开讨论。
根据以前的讨论,当前为pending时,须要等到它肯定时,promise2的状态才有可能肯定。因此MyPromise里这部分代码这样写
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
// 声明一个成功函数
function successFn(value) {
let x = onResolved(value)
}
// 声明一个失败函数
function failFn(reason) {
let x = onRejected(reason)
}
// 将成功函数push到当前实例的resolvedCallbacks
this.resolvedCallbacks.push(successFn)
// 将失败函数push到当前实例的rejectedCallbacks
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
})
// onResolved(this.data)
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
})
// onRejected(this.reason)
}
return promise2
}
复制代码
解释一下上面代码的意思:若是当前Promise的then调用时状态为pending时,咱们声明successFn和failFn,而且把它们分别push到resolvedCallbacks和rejectedCallbacks里。这样,successFn和failFn的执行时机就交给了当前promise实例。当当前Promise实例状态肯定时,successFn或者failFn就会被执行,这样就能够经过调用onResovled或者onRejected拿到回调的结果了。
这一步很是关键,若是当前Promise实例状态为pending时,then方法里返回新的promise2就必须等到它状态肯定时才能拿到它成功或者失败回调的值。而后根据回调执行后的结果x来肯定promise2的状态。
整个流程的顺序以下:
if(this.status === 'pending')
分支里fulfilled
或者rejected
这个简单,直接调用onResovled或者onRejected,也就是当前实例then传递的第一个和第二个参数。
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
}
function failFn(reason) {
let x = onRejected(reason)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
// 由于此时当前实例的resolve或者reject已经执行
// this.data或者this.reason
let x = onResolved(this.data)
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
// 此时当前实例resolve或者reject已经执行
let x = onRejected(this.reason)
})
}
return promise2
}
复制代码
此时,当前实例的resolve或者reject已经执行,状态已经肯定,this.data或者this.reason已经有值,直接用then传递的onResoved或者onRjected调用便可获取x。
咱们的then写到这里,promise2它已经拿到了当前实例的回调结果了。咱们看一下实际使用中它在哪里 。请看下面的例子:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
function onResolved(res) {
// 这里进行处理
return xxx // 这里返回的xxx其实就是上面代码里的x
}
function onRejected(err) {
// 处理
return xxx // 这里返回的xxx就是上面代码里的x
}
let promise2 = promise1.then(onResolved, onRjected)
复制代码
到这里,当then方法执行时,咱们已经成功拿到了当前实例的回调值x,接下来,咱们将对这个值进行统一处理,并根据x来调用promise2的构造时的resolve或者reject方法来肯定promise2的状态。
在规范里,关于如何处理x来肯定promise2的状态有一个专门的章节来论述。它在规范的2.3节,称为The Promise Resolution Procedure ,它根据x的可能的值进行针对处理。x能够是如下的值:
咱们须要写一个函数resolve_promise
来对x进行处理,并肯定promise2的状态
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
// 注意这里
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
// 这里也有
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
let x = onResolved(this.data)
// 这里也有哦
resolve_promise(promise2, x, resolve, reject)
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
// 还有这里哦
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
}
return promise2
}
// 这里是resolve_promise
function resolve_promise(promise2, x, resolve, reject) {
}
复制代码
要写的resolve_promise接收4个参数,一个是当前正在构造的promise2实例,一个是经过当前回调拿到的结果x,resolve和reject是用来肯定promise2状态的两个方法,由于只有resolve或者reject被调用了,promise2的状态才能肯定嘛 。
接下来,咱们根据x可能的四种状态,来分别处理,这些都是规范的具体内容:
这种状况只有在当前实例是pending情况下才有可能发生,例子以下:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
function onResolved(res) {
return promise2
}
let promise2 = promise1.then(onResolved)
promise2.then(res => {
console.log(res)
}, err => {
console.log(err)
// 这里会打印出 TypeError: Chaining cycle detected for promise
})
复制代码
因此当这种状况发生时,根据规范,直接把promise2使用reject拒绝,并传一个TypeError
function resolve_promise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return //这里return不用再往下了
}
}
复制代码
使用场景是这种状况:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
function onResolved(res) {
// 这里返回的promise就是咱们要处理的x
return new Promise(function(resolve, reject) {
reject('fail')
})
}
let promise2 = promise1.then(onResolved)
复制代码
这个时候根据规范,promise2的状态和值使用x的状态和值,分三种状况:
此时的resolve_promise方法进行以下更改:
function resolve_promise(promise2, x, resolve, reject) {
// x和promise2引用相同的状况
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
// 若是x是MyPromise的实例
if (x instanceof MyPromise) {
x.then(function (v) {
resolve_promise(promise2, v, resolve, reject)
}, function (t) {
reject(t)
}
return
}
}
复制代码
这里你可能看不懂,不是说有三种状况吗?这个代码也没有针对这三种状况作判断呀。是这样的,规范上说的三种状况我经过一个x.then
就能够拿到x构造时resolve或者reject函数传递的值。这里真正的坑点在于,x在构造时使用resolve或者reject传值时也可能传递了一个promise实例,并且仍是规范其它实现(好比bluebird或者Q)的promise实例,因此这里才须要使用递归。这个坑点在规范里没说,可是若是不这样写,有不少测试用例通不过。
根据规范:
let then = x.then
,若是在这个过程出现抛出了异常,就reject(e)
then
是一个函数
reject(r)
resolve(x)
你可能看不懂规范上说的这些空间是干吗的,其实它是对Promise实现方案作的一个兼容处理。咱们知道,Promise并无官方实现,只有规范和测试用例,它有多种实现,好比bluebird和Q,若是我在使用时,同时用了bluebird和Q,就须要作兼容处理。
这里的x就多是bluebird或者Q的实例。
那我如何判断它是其它Promise的实现呢?看它有没有一个then方法,全部Promise实现都有合乎规定的then方法。若是有then就会走到这里的逻辑。若是x是一个包含then方法的普通对象,也会走到这里。因此这里才会有这么多的判断,还有递归。
直接写MyPromise:
function resolve_promise(promise2, x, resolve, reject) {
// x就是promise2的状况
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
// x是MyPromise实例的状况
if (x instanceof MyPromise) {
x.then(function(v) {
resolve_promise(promise2, v, resolve, reject)
}, function(t) {
reject(t)
})
return
}
// x 是对象或者函数
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
// 开关
// 控制resolvePromise和rejectPromise还有catch里reject的调用
let called = false
try { // x.then可能有异常,须要捕获
let then = x.then
if (typeof then === 'function') {
// 有then方法,则调用,若是then方法并无实际resolvePromise
// 或者rejectPromise参数的话,promise2永远都是pending状态
// 由于resolve和reject永远都不可能执行
then.call(x, function resolvePromise(y) {
if (called) return
called = true
resolve_promise(promise2, y, resolve, reject)
}, function rejectPromise(r) {
if (called) return
called = true
reject(r)
})
} else {
// 若是then不是一个函数直接resolve
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
复制代码
看到这里,你就能够找出Promise的一个"bug"了,请看下面场景的代码:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
let promise2 = promise1.then(function(res) {
// 这里返回一个包括then方法的对象
// 可是这个then方法啥都没作
// 致使promise2在构造过程当中resolve或者reject永远都没执行
return {
then: function() {}
}
})
promise2.then(res => {
// 这里永远都不会执行
console.log(res)
})
// promise2永远都是pending状态
console.log(promise2)
复制代码
你能够把上面的代码粘到浏览器里试一下~
那直接resolve(x)
便可,在上面已经有了。
请看咱们已经写的then函数里:
MyPromise.prototype.then = function (onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
let x = onResolved(this.data)
// 看这里,看下面这行
resolve_promise(promise2, x, resolve, reject)
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
let x = onRejected(this.reason)
// 看这里,看下面这行
resolve_promise(promise2, x, resolve, reject)
})
}
return promise2
}
复制代码
在this.status === 'fulfilled'
和this.status === 'rejected'
这两个分支里执行resolve_promise
是拿不到promise2这个实例的,由于它没还构造完成,是undefined ,因此这里要加个setTimeout才行。并且,根据规范,onResolved和onRejected必须异步执行呢。
注意,this.stauts === 'pending'
的那个不用哦,由于它执行的时候就是异步的。
改为下面这样:
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
})
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
})
}
复制代码
其实,如今咱们已经基本写出一个Promise了!可是你能够会问,Promise.all
Promise.race
实例上的catch
等方法如今尚未啊!不用担忧,只要完成了then,一个Promise就完成了百分之八十了,以上常的的几个API都是小意思,要实现分分钟~ 固然,它还有点小瑕疵,这些问题,还有测试,咱们下篇文章完成!
完成的代码在这里:
function MyPromise(executor) {
this.status = 'pending'
this.data = undefined
this.reason = undefined
this.resolvedCallbacks = []
this.rejectedCallbacks = []
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.data = value
this.resolvedCallbacks.forEach(fn => fn(this.data))
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectedCallbacks.forEach(fn => fn(this.reason))
}
}
executor(resolve, reject)
}
MyPromise.prototype.then = function (onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
})
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
})
}
return promise2
}
function resolve_promise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
if (x instanceof MyPromise) {
x.then(function(v) {
resolve_promise(promise2, v, resolve, reject)
}, function(t) {
reject(t)
})
return
}
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, function resolvePromise(y) {
if (called) return
called = true
resolve_promise(promise2, y, resolve, reject)
}, function rejectPromise(r) {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
复制代码
感谢您的阅读!