前言
在平时工作中,我们经常用promise来解决异步回调问题。你知道promise是如何实现的吗?
本文采用promises/A+规范来实现promise。
这是Promises/A+规范文档。
这是中文翻译。
源码仓库:github
基本状态问题
首先我们来开一个简单的promise的例子:
new Promise((resolve, reject) => {
resolve('ok')
}).then((value) => {
console.log(value) // ok
})
通过这个例子我们来了解几个promise的概念
-
- 当使用promise时,会传入一个执行器,执行器传入两个函数(resolve, reject),此执行器是立即执行的;
-
- 有三种状态:成功(fulfilled),失败(rejected),等待(pending);
-
- 执行器调用resolve走成功态,如果调用reject或发生异常,走失败态;如果执行器抛异常,走失败态;
-
- promise状态一旦改变,以后不能更改
- 我们首先来定义一个最基础的结构。
// promise的三种状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
// 传入一个执行器
constructor(executor) {
const resolve = (value) => {}
const reject = (reason) => {}
// 该执行是立即执行的,并且会传入两个函数resolve, reject
executor(resolve, reject)
}
then(onFulfilled, onRejected) {}
}
- 接着,实现改变状态。
// promise的三种状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
// 传入一个执行器
constructor(executor) {
// 初始状态是等待状态
this.state = PENDING
const resolve = (value) => {
/**
* 1. 调用resolve方法状态会改为FULFILLED
* 2. 由于promise状态一旦改变,以后不能更改,所以只有在PENDING状态时才能改为FULFILLED
*/
if (this.state === PENDING) {
this.state = FULFILLED
// 成功态时的数据
this.value = value
}
}
const reject = (reason) => {
/**
* 1. 调用resolve方法状态会改为REJECTED
* 2. 由于promise状态一旦改变,以后不能更改,所以只有在PENDING状态时才能改为REJECTED
*/
if (this.state === PENDING) {
this.state = REJECTED
// 失败态时的原因
this.reason = reason
}
}
/**
* 该执行是立即执行的,并且会传入两个函数resolve, reject。
* 如果执行器抛异常,走失败态。所以加上try/catch
*/
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
// 根据状态调用不同的回调函数
if (this.state === FULFILLED) {
onFulfilled(this.value)
}
if (this.state === REJECTED) {
onRejected(this.reason)
}
}
}
到这里,我们实现了最基本的promise。
promise异步问题
以上代码,有什么问题吗?请看下面的例子,我们使用setTimeout
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 1000)
}).then((value) => {
console.log(value) // ok
})
此时,发现then方法的回调没有打印ok。原因就是因为setTimeout是异步方法。那如何解决这个问题? 我们这时就可以用发布订阅的思想来解决。因为promise的状态改变可能是异步方法里面,所以可以在then方法里,如果当前状态是pending状态时先收集回调,然后等状态改变的时候(调用resolve或reject时),将一系列回调方法执行。
// promise的三种状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
// 传入一个执行器
constructor(executor) {
// 初始状态是等待状态
this.state = PENDING
this.resolvedCallbacks = [] // 存放成功的回调
this.rejectedCallbacks = [] // 存放失败的回调
const resolve = (value) => {
/**
* 1. 调用resolve方法状态会改为FULFILLED
* 2. 由于promise状态一旦改变,以后不能更改,所以只有在PENDING状态时才能改为FULFILLED
*/
if (this.state === PENDING) {
this.state = FULFILLED
// 成功态时的数据
this.value = value
// 异步回调
this.resolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
/**
* 1. 调用resolve方法状态会改为REJECTED
* 2. 由于promise状态一旦改变,以后不能更改,所以只有在PENDING状态时才能改为REJECTED
*/
if (this.state === PENDING) {
this.state = REJECTED
// 失败态时的原因
this.reason = reason
// 异步回调
this.rejectedCallbacks.forEach(fn => fn())
}
}
/**
* 该执行是立即执行的,并且会传入两个函数resolve, reject。
* 如果执行器抛异常,走失败态。所以加上try/catch
*/
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
// 根据状态调用不同的回调函数
if (this.state === FULFILLED) {
onFulfilled(this.value)
}
if (this.state === REJECTED) {
onRejected(this.reason)
}
// pending状态,异步调用,需要把回调存起来
if (this.state === PENDING) {
this.resolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.rejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
then方法的几个问题
-
- 链式调用
then()
方法返回的是一个新的promise,因此后面可继续调用then
- 链式调用
new Promise((resolve, reject) => {
resolve('ok')
}).then((value) => {
console.log(value) // ok
return 'abc'
}).then((value) => {
console.log(value) // abc
})
-
then(onFulfilled, onRejected)
中,回调方法的返回值问题:
-
- 返回普通值(x),则直接传递下一个then中的onFulfilled,即直接resolve(x)
-
- 返回Promise,则需要根据Promise的状态来决定下一个then是使用onFulfilled还onRejected回调
new Promise((resolve, reject) => {
resolve('ok')
}).then((value) => {
console.log(value) // ok
return new Promise((resolve, reject) => {
reject('123')
})
}).then(null, (value) => {
console.log(value) // 123
})
-
- 透传问题,then方法的两个回调onFulfilled, onRejected可能都为空
new Promise((resolve, reject) => {
resolve('ok')
}).then().then().then().then((value) => {
console.log(value) // ok
})
解决链式调用和then回调中返回普通值的问题
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
// 根据状态调用不同的回调函数
if (this.state === FULFILLED) {
// 返回普通值(x),则直接传递下一个then中的onFulfilled,即直接resolve(x), 下同
let x = onFulfilled(this.value)
resolve(x)
}
if (this.state === REJECTED) {
let x = onRejected(this.reason)
resolve(x)
}
// pending状态,异步调用,需要把回调存起来
if (this.state === PENDING) {
this.resolvedCallbacks.push(() => {
let x = onFulfilled(this.value)
resolve(x)
})
this.rejectedCallbacks.push(() => {
let x = onRejected(this.reason)
resolve(x)
})
}
})
return promise2
}
then回调方法中返回的是promise。
首先把resolve(x)
抽离成一个新的函数function resolvePromise(promise2, x, resolve, reject)
,该函数我们会解决以下几个问题:
-
- 如果
promise2 === x
需要reject一个类型错误。因为promise2 === x
会死循环,想象一下,promise2在等待x的状态的改变,而x又是promise2,你在等我改变状态,我也在等你改变状态,是不是就陷入死循环了呢。
- 如果
-
- 判断x是否为promise。首先x必须是对象或函数,然后还必须有
then方法
- 判断x是否为promise。首先x必须是对象或函数,然后还必须有
resolvePromise函数
function resolvePromise(promise2, x, resolve, reject) {
// 1. 如果promise2 === x,需要reject一个类型错误
if (x === promise2) {
return reject(new TypeError(`Chaining cycle detected for promise #<Promise>`))
}
// 是对象或函数
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
// 必须有then方法
let then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
resolve(y)
}, (r) => {
reject(r)
})
} else { // 没有then方法按普通值处理
resolve(x)
}
} else { // 普通值
resolve(x)
}
}
then方法修改为:
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
// 根据状态调用不同的回调函数
if (this.state === FULFILLED) {
// 这里需要加上的setTimeout,才能访问到promise2,下同
setTimeout(() => {
// 返回普通值(x),则直接传递下一个then中的onFulfilled,即直接resolve(x), 下同
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
})
}
if (this.state === REJECTED) {
setTimeout(() => {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
})
}
// pending状态,异步调用,需要把回调存起来
if (this.state === PENDING) {
this.resolvedCallbacks.push(() => {
setTimeout(() => {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
})
})
this.rejectedCallbacks.push(() => {
setTimeout(() => {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
})
})
}
})
return promise2
}
上面的resolvePromise函数,还有几个以下问题:
- 在取then方法的时候,可能报错,因此需要try/catch捕获并reject
- 保证then方法的回调只被调用一次
- y可能继续返回一个promise,因此需要使用递归,直到y返回普通值为止。 修改如下:
function resolvePromise(promise2, x, resolve, reject) {
// 1. 如果promise2 === x,需要reject一个类型错误
if (x === promise2) {
return reject(new TypeError(`Chaining cycle detected for promise #<Promise>`))
}
// 是对象或函数
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
let called = false // 保证只调用一次
try {
// 必须有then方法
let then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
if (called) return
called = true
// resolve(y)
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (called) return
called = true
reject(r)
})
} else { // 没有then方法按普通值处理
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else { // 普通值
resolve(x)
}
}
透传问题
这个比较容易解决,直接上代码
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
const promise2 = new Promise((resolve, reject) => {
.....
return promise2
}
then方法还有一个问题,就是onFulfilled,onRejected可能抛出异常,因此需要捕获。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
const promise2 = new Promise((resolve, reject) => {
// 根据状态调用不同的回调函数
if (this.state === FULFILLED) {
// 这里需要加上的setTimeout,才能访问到promise2,下同
setTimeout(() => {
try {
// 返回普通值(x),则直接传递下一个then中的onFulfilled,即直接resolve(x), 下同
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.state === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// pending状态,异步调用,需要把回调存起来
if (this.state === PENDING) {
this.resolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.rejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
测试
到这里,我们就完成了promises/A+规范了,如何测试了。
- 按照测试包promises-aplus-tests
npm i promises-aplus-tests -g
- 实现deferred
// promises-aplus-tests
Promise.deferred = function() {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise
- 开始测试
假设你的代码文件名为myPromise.js
promises-aplus-tests myPromise.js
perfect! 872个测试全部通过
promise的一些方法
在上面的实现promises/A+规范中,并没有规定resolve(promise),为了和es6的promise表现一致,修改修改一下:
const resolve = (value) => {
// 下面的这个规范没有要求
if (value instanceof Promise) {
return value.then(resolve, reject)
}
/**
* 1. 调用resolve方法状态会改为FULFILLED
* 2. 由于promise状态一旦改变,以后不能更改,所以只有在PENDING状态时才能改为FULFILLED
*/
if (this.state === PENDING) {
this.state = FULFILLED
// 成功态时的数据
this.value = value
// 异步回调
this.resolvedCallbacks.forEach(fn => fn())
}
}
Promise.resolve
Promise类方法,将一个对象转为一个Promise,状态为fulfilled
static resolve(data) {
return new Promise((resolve) => {
resolve(data)
})
}
Promise.reject
Promise类方法,将一个对象转为一个Promise,状态为rejected
static reject(data) {
return new Promise((resolve, reject) => {
reject(data)
})
}
Promise.prototype.catch
catch方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。返回一个新的promise
catch(fn) {
return this.then(null, fn)
}
Promise.prototype.finally
此方法返回一个Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。 下面是finally的使用方法
new Promise((resolve, reject) => {
resolve('ok')
}).then((value) => {
return 'abc'
}).finally(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('def')
}, 1000)
})
}).then((value) => {
// 会等待1s,但value 是abc不是def
console.log(value) // abc
})
实现:
finally(fn) {
return this.then((value) => {
return Promise.resolve(fn()).then(() => value)
}, (err) => {
return Promise.resolve(fn()).then(() => { throw err })
})
}
Promise.all
将多个Promise放在一个数组中,当整个数组的全部promise成功时才会返回成功, 当数组中的promise有一个出现失败时就返回失败 (失败的原因是第一个失败promise的结果)。
返回的是一个promise
先看一个使用的例子
const p1 = '1111'
const p2 = Promise.resolve('22222')
const p3 = new Promise((resolve) => {
setTimeout(() => {
resolve('3333')
}, 2000)
})
const p4 = new Promise((resolve) => {
setTimeout(() => {
resolve('4444')
}, 1000)
})
Promise.all([p1, p2, p3, p4]).then(value => {
console.log(value) // [ '1111', '22222', '3333', '4444' ]
})
代码实现
static all(promises) {
return new Promise((resolve, reject) => {
let result = []
let times = 0
const processSuccess = (index, value) => {
result[i] = value
if (++times === promises.length) {
resolve(result)
}
}
for (let i = 0; i < promises.length; i++) {
let p = promises[i]
// 判断p是否为promise,即判断有没有then方法
if (p && typeof p.then === 'function') {
p.then((value) => {
processSuccess(i, value)
}, reject)
} else {
processSuccess(i, p)
}
}
})
}
Promise.race
该方法返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。 它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
实现
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const p = promises[i]
if (p && typeof p.then === 'function') {
p.then(resolve, reject)
} else {
resolve(p)
}
}
})
}
Promise.allSettled
返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。
比如:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: 'aaaaa'
})
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: 'bbbbb'
})
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('404')
}, 1500)
})
const p4 = '1111'
Promise.allSettled([p1, p2, p3, p4]).then((value) => {
console.log('value', value)
}, (reason) => {
console('reason', reason)
})
/**
输出
value [
{ status: 'fulfilled', value: { data: 'aaaaa' } },
{ status: 'fulfilled', value: { data: 'bbbbb' } },
{ status: 'rejected', reason: '404' },
{ status: 'fulfilled', value: '1111' }
]
**/
代码实现
static allSettled(promises) {
let result = []
let times = 0
return new Promise((resolve, reject) => {
const processComplete = (index, data, state) => {
result[index] = {
state,
...data
}
if (++times === promises.length) {
resolve(result)
}
}
for (let i = 0; i < promises.length; i++) {
const p = promises[i]
if (p && typeof p.then === 'function') {
p.then((value) => {
processComplete(i, { value }, 'fulfilled')
}, (reason) => {
processComplete(i, { reason }, 'rejected')
})
} else {
processComplete(i, { value: p }, 'fulfilled')
}
}
})
}
Promise.any
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
注意:
Promise.any()
方法依然是实验性的,尚未被所有的浏览器完全支持。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
代码实现如下:
static any(promises) {
let result = []
let resolveValue
let hasResolved = false
let times = 0
return new Promise((resolve, reject) => {
const processFail = (index, reason) => {
result[index] = reason
if(++times === promises.length) {
reject(new AggregateError(result, 'All promises were rejected'))
}
}
const processSuccess = (index, value) => {
if (!hasResolved) {
resolveValue = value
hasResolved = true
}
if(++times === promises.length) {
resolve(resolveValue)
}
}
for (let i = 0; i < promises.length; i++) {
let p = promises[i]
p.then((value) => {
processSuccess(i, value)
}, (reason) => {
processFail(i, reason)
})
}
})
}