为何需要 promise ?
在 promise 出现之前,异步编程由回调函数完成,很容易出现回调嵌套过多,也即常提到的“回调地狱”。回调地狱不仅是可读性差,维护起来也相当麻烦,如果某个环节出错了,经常无法准确定位问题。
Promise 正是为了解决这些问题而出现,链式调用解决了回调地狱的问题,它的错误传播机制实现了统一的错误信息处理。可以看一个 MDN 上的例子:
// 回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// Promise 方案
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
动手实现
由于 ES6 中的 Promise 遵从 Promise A+ 规范,接下来就根据其主要的几个规则,开始一起动手实现吧!
我们先根据 Promise 的调用方式搭建它的构造函数:
class Promise {
constructor (excutor) {
this.value = null
this.reason = null
const resolve = (value) => {
this.value = value
}
const reject = (reason) => {
this.reason = reason
}
try {
excutor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
}
增加状态机
每一个 Promise 实例只能有三个状态:pending,fulfilled,rejected,且状态之间的转换只能是 pending => fulfilled 或 pending => rejected 。我们接着实现这个功能:
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (excutor) {
this.value = null
this.reason = null
this.state = PENDING
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
}
}
try {
excutor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
}
实现 then 方法
Promise 中的 then 方法接受两个函数作为参数,分别是 Promise 成功和失败的回调。所以在 then 方法中,先判断 Promise 的状态,如果是成功,就将 this.value 传入回调并执行;如果失败,将 this.reason 传入回调并执行(所以需要全局的 value 和 reason)。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (excutor) {
...
}
then (onFulfilled, onRejected) {
if (this.state === FULFILLED) {
onFullfilled(this.value)
} else {
onRejected(this.reason)
}
}
}
实现异步调用
目前我们的实现只是同步的,思考一下下面例子中 then 中的回调函数会不会执行?
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000)
})
// 回调会执行吗?
p1.then((v) => {
console.log('success')
})
例子中的 resolve 函数是异步执行的,而 then 是同步执行的,也就是说 then 会先于 resolve 执行,那 then 执行的时候 Promise 的状态还是 Pending。所以,then 中的回调函数是不会执行的。接下来我们做一些修改来解决这个问题:
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (excutor) {
this.value = null
this.reason = null
this.state = PENDING
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value
this.state = FULFILLED
this.onFulfilledCallbacks.forEach(onFulfilledCallback => {
onFulfilledCallback()
})
}
}
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason
this.state = REJECTED
this.onRejectedCallbacks.forEach(onRejectedCallback => {
onRejectedCallback()
})
}
}
try {
excutor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
then (onFulfilled, onRejected) {
if (this.state === FULFILLED) {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value)
})
} else {
this.onRejectedCallbacks.push(() => { onRejected(this.reason)
})
}
}
}
实现链式调用
在 Promise A+ 规范中,then 函数是可以链式调用的,也就是说 then 函数的返回值也是一个 Promise,且这个 Promise 的 resolve 值是上一个 Promise 的 onFulfilled 函数的返回值,或 onRejected 函数的返回值。而如果上一个 Promise 的执行过程中发生错误,那么这个错误将被作为返回的 Promise 的 ``onRejected 函数的参数传入。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
...
resolvePromise (promise, x, resolve, reject) {
if (promise === x) {
reject(new TypeError('Chaining cycle'))
return
}
if (x instanceof APromise) {
if (x.state === PENDING) {
x.onFulfilledCallbacks.push(() => {
resolve(x.value)
})
} else if (x.state === FULFILLED) {
resolve(x.value)
} else if (x.state === REJECTED) {
reject(x.reason)
}
} else if (x && typeof x === 'object' || typeof x === 'function') {
let used
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, y => {
if (used) return
used = true
this.resolvePromise(promise, y, resolve, reject)
}, r => {
if (used) return
used = true
reject(r)
})
} else {
if (used) return
used = true
resolve(x)
}
} catch (reason) {
if (used) return
used = true
reject(reason)
}
} else {
resolve(x)
}
}
then (onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
this.onRejectedCallbacks.push(() => {
if (typeof onRejected !== 'function') {
reject(this.reason)
} else {
try {
let x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
}
})
} else if (this.state === FULFILLED) {
try {
let x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
} else {
try {
let x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
}
})
return promise2
}
}
我们已经知道 then 方法会返回一个 Promise 对象,且这个对象的 resolve 值是前一个 Promise 的 onFulfilled 函数的返回值,或 onRejected 函数的返回值。但是当返回值是 thenable 对象,或是 Promise 时,需要有特殊处理。具体的逻辑处理在 resolvePromise(promise, x, resolve, reject) 方法中,我们接下来结合规范来看它的执行过程 :
- 如果
promise和x是同一个对象,那抛出一个Chaining cycle的TypeError - 如果
x是一个Promise对象,那么promise将使用x的状态x处于PENDING状态,promise也将保持PENDING状态直到x被resolve或rejectx处于FULFILLED状态,用x的value来resolve promisex处于REJECTED状态,用同样的reason来reject promise
- 如果
x是一个对象或函数,将then指向x.then- 如果
then是一个函数,那调用它,并将x作为它的this,以resolvePromise作为第一个参数,rejectPromise作为第二个参数,且:- 如果
resolvePromise以一个y值被调用了,则执行resolvePromise(promise, x, resolve, reject) - 如果
resolvePromise以一个r值被拒绝了,则执行reject(r) - 如果
resolvePromise和rejectPromise都被调用了,或者某个已被被多次调用了,则首次发生的调用生效,其余所有调用都被忽略。 - 如果在
then调用过程中发生了错误,如果resolvePromise和rejectPromise都没被调用过,则reject这个错误;否则忽略这个错误
- 如果
- 如果
then不是函数,则以x来resolve promise
- 如果
- 上述过程中,如果发生异常,用异常来
reject promise
实现 catch 的异常处理
考虑这个场景:promise 被 reject 且后续的 then 方法没有传入 onRejected 处理函数,那么根据 then 中的处理逻辑,第二个 promise 的 reason 就是前一个promise 的 reason 值。所以 throw 方法中只要调用 this.then(null, onRejected) 就能处理第一个 promise 未被处理的 reject。
class Promise {
...
throw (onRejected) {
return this.then(null, onRejected)
}
}