Promise
使用
Promise
时,有三种状态Promise
是一个构造函数,用来生成Promise实例
- 构造函数有两个参数,分别是
resolve
和reject
。它们是两个函数,由 JS 引擎提供resolve
函数的作用是,将Promise对象
的状态从pending
变为fulfilled
。在异步操作成功时调用,并将异步操作的结果,作为参数传递出去reject
函数的作用是,将Promise对象
的状态从pending
变为reject
。在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去 ```javascript // 案例一 const promise = new Promise(function(resolve, reject) { // … some code
if (/ 异步操作成功 /){ resolve(value); } else { reject(error); } }); ```
Promise 实例上的方法
- 构造函数有两个参数,分别是
Promise.prototype.then((result) => {}, (reason) => {})
- 第一个参数是
fulfilled状态
的回调函数 - 第二个参数是
rejected状态
的回调函数 - 该方法返回的是一个
新的Promise实例
,方便链式调用 ```javascript // 案例一 let random = Math.floor(Math.random() * 100); const promise = new Promise((resolve, reject) => { if (random % 2 === 0) { resolve(‘偶数’) console.log(123) // resolve()执行后,之后的代码还是会执行 } else { reject(‘奇数’) console.log(456) // reject()执行后,之后的代码还是会执行 } })
- 第一个参数是
// 链式调用 promise.then( (result) => { console.log(result); return result + ‘1’; }, (reason) => { console.log(reason); return reason + ‘1’; }, ).then( (result) => console.log(result), // 上一个then里,return后都到这个回调函数里 (reason) => console.log(reason) // 这个函数不会被用到 )
- `Promise.prototype.catch(error => {})`
- 作用:`.then(null, rejection)`或`.then(undefined, rejection)`的别名,用于指定发生错误时的回调函数
- `catch()`方法返回的还是一个 Promise 对象,因此后面还可以接着调用`then()`方法
```javascript
// 案例二
let random = Math.floor(Math.random() * 100);
const promise = new Promise((resolve, reject) => {
if (random % 2 === 0) {
resolve('偶数')
} else {
reject('奇数')
}
})
promise.then(
(result) => { console.log(result); throw new Error(result); },
).then(
(result) => console.log(result),
).catch(err => console.log(err))
Promise.prototype.finally(()=>{})
- 不管 Promise 对象最后状态如何,都会执行的操作
finally
本质上是then
方法的特例- 该方法是 ES2018 引入标准的 ```javascript const promise = new Promise((resolve, reject)=> …);
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
```javascript
// Promise.prototype.finally的实现
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
Promise 构造函数上的方法
Promise.resolve(any)
- 作用:将现有对象
any
转为 Promise 对象,状态为fulfilled
- 参数
any
- 参数是一个 Promise 实例
- 将不做任何修改、原封不动地返回这个实例
- 参数是一个
thenable对象
(指的是具有then
方法的对象)- 将这个对象转为 Promise 对象,然后就立即执行
thenable对象
的then()
方法
- 将这个对象转为 Promise 对象,然后就立即执行
- 参数不是具有
then()
方法的对象,或根本就不是对象- 返回一个新的 Promise 对象,状态为
fulfilled
- 返回一个新的 Promise 对象,状态为
- 不带有任何参数
- 直接返回一个
fulfilled
状态的 Promise 对象
```javascript // 案例二:有then方法的对象 let thenable = { then: function(resolve, reject) { resolve(42); } };// 案例一
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
- 直接返回一个
- 参数是一个 Promise 实例
- 作用:将现有对象
let p1 = Promise.resolve(thenable); p1.then(function (value) { console.log(value); // 42 });
- `Promise.reject(reason)`
- 返回一个新的 Promise 实例,该实例的状态为`rejected`
- 参数`any`
- 参数是一个 Promise 实例
- 将不做任何修改、原封不动地返回这个实例
- 参数是一个`thenable对象`(指的是具有`then`方法的对象)
- 将这个对象转为 Promise 对象,然后就立即执行`thenable对象`的`then()`方法
- 参数不是具有`then()`方法的对象,或根本就不是对象
- 返回一个新的 Promise 对象,状态为`rejected`
- 不带有任何参数
- 直接返回一个`rejected`状态的 Promise 对象
<a name="Yecff"></a>
## 手写 Promise
Promise 的实现标准是[ Promise/A+](https://promisesaplus.com/),该标准只规定了 then,没有规定 all、catch、race,也没有规定构造函数<br />Promise/A+ 的实现参考文献汇总 [Promise Implementations](https://promisesaplus.com/implementations)<br />我们从 promise 的使用方式开始,一步步实现 promise
<a name="uXHK1"></a>
### new Promise()
```javascript
// new MyPromise()
new MyPromise((resolve, reject) => {
resolve(123)
// reject(456)
})
一个 promise 实例必须是这三种状态其中之一:pending、fulfilled、rejected
2.1.1 当 promise 实例状态是 pending 时
2.1.1.1 promise 实例状态可以转变为 fulfilled、rejected 其中之一
2.1.2 当 promise 实例状态是 fulfilled 时
2.1.2.1 promise 实例不可以转变为其他状态
2.1.2.2 promise 实例必须有一个 value,且不可以改变
2.1.3 当 promise 实例状态是 rejected 时
2.1.2.1 promise 实例不可以转变为其他状态
2.1.2.2 promise 实例必须有一个 reason,且不可以改变
class MyPromise {
// Promise 三个状态
static PENDING = 0
static FULLFILLED = 1
static REJECTED = 2
transition(state, value) {
// 状态 state 只能从 PENDING ——> FULLFILLED 或 PENDING ——> REJECTED
if (this.state === state
|| this.state === MyPromise.FULLFILLED
|| this.state === MyPromise.REJECTED) {
return false
}
this.state = state
this.value = value
return true
}
// 实现 resolve
resolve(data) {
// 状态变化: PENDING ——> FULLFILLED
this.transition(MyPromise.FULLFILLED, data)
}
// 实现 reject
reject(reason) {
// 状态变化: PENDING ——> REJECTED
this.transition(MyPromise.REJECTED, reason)
}
constructor(executor) {
this.state = MyPromise.PENDING
this.value = undefined
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch(error) {
this.state = MyPromise.REJECTED
console.error(error)
}
}
}
const promise1 = new MyPromise((resolve, reject) => {
resolve(123)
})
const promise2 = new MyPromise((resolve, reject) => {
reject(456)
})
// 能被内部 try catch 捕获
const promise3 = new MyPromise((resolve, reject) => {
throw new Error('I am Error')
})
// 不能被内部 try catch 捕获
const promise4 = new MyPromise((resolve, reject) => {
setTimeout(() => {
throw new Error('I am Error')
})
})
:::info
为什么 MyPromise 中的异步事件 setTimeout 报错,MyPromise 内部的 trycatch 捕获不到?
答:因为 try catch 的执行是同步的,异步事件执行出错 catch 捕获不到
:::
try {
setTimeout(()=>{
throw new Error('error')
})
} catch (error) {
console.error(error)
}
class MessageCenter {
constructor() {
this.center = {}
}
// 注册事件
subscribe(type, callback) {
if (!this.center[type]) this.center[type] = [callback]
else this.center[type].push(callback)
}
// 注销事件
unSubscribe(type, callback) {
const index = this.center[type].indexOf(callback)
if (index !== -1) this.center[type].splice(index, 1)
}
// 发布消息
public(type, ...args) {
if (this.center[type]) this.center[type].forEach(callback => callback(...args))
}
}
const messageCenter = new MessageCenter()
try {
function changeHandler(...args) {
console.log(...args)
throw Error('I am Error')
}
messageCenter.subscribe('change', changeHandler)
} catch (error) {
console.error(error)
}
messageCenter.public('change', 456)
then()
new Promise((resolve, reject) => {
resolve(123)
})
.then(
data => console.log(data),
)
.then(
'data',
reason => console.error(reason)
)
.then(
data => console.log(data),
reason => console.error(reason)
)
根据 Promise/A+ 的 then 方法标准
一个 Promise 实例提供一个 then 方法,用于接收它 现在或最终的 值 或 错误原因
promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled
和 onRejected
都是可选参数
- 如果
onFulfilled
不是函数,就忽略 - 如果
onRejected
不是函数,就忽略
2.2.2 如果onFulfilled
是函数
- 它必须在 promise 状态变为 fulfilled 时才能执行,并且将
promise
的值作为它第一个参数 - 在 promise 状态变为 fulfilled 之前,它不能被执行
- 它只能被执行一次
2.2.3 如果onRejected
是函数
- 它必须在 promise 状态变为 rejected 时才能执行,并且将
promise
的 错误原因 作为它第一个参数 - 在 promise 状态变为 rejected 之前,它不能被执行
- 它只能被执行一次
2.2.4 当执行上下文栈仅包含平台代码(platform code)时,onFulfilled
或 onRejected
才能被执行
:::info
platform code 指的是:JS 解析引擎、环境 和 promise 的实现代码。
在实践中,这个要求能保证在事件循环执行到 then
函数时,onFulfilled
和 onRejected
的执行是异步的,并使用一个新的栈。
这个 platform code 的实现可以用宏任务机制,比如[setTimeout](https://html.spec.whatwg.org/multipage/webappapis.html#timers)
或 setImmediate
,也可以用微任务机子来实现,比如[MutationObserver](https://dom.spec.whatwg.org/#interface-mutationobserver)
或 [process.nextTick](https://nodejs.org/api/process.html#process_process_nexttick_callback)
:::
2.2.5 onFulfilled
和 onRejected
必须作为函数被调用
2.2.6 then
方法可以在同一个 promise 实例中,多次调用
- 当
promise
的状态是 fulfilled,所有onFulFilled
回调函数,必须按照它们各自所在then
函数的顺序,依次执行 - 当
promise
的状态是 rejected,所有onRejected
回调函数,必须按照它们各自所在then
函数的顺序,依次执行
2.2.7 then
必须返回一个 promise 实例
- 不论是
onFulfilled
还是onRejected
返回了一个值x
,执行 Promise 决议程序,即[[Resolve]](promise2, x)
- 不论是
onFulfilled
还是onRejected
抛出一个异常e
,promise2
必须以e
作为错误原因变为 rejected 状态 - 如果
onFulfilled
不是函数,并且promise1
状态为 fulfilled。promise2
必须以promise1
的value 作为 value,并变为 fulfilled 状态 - 如果
onRejected
不是函数,并且promise1
状态是 rejected。promise2
必须以promise2
的 reason 作为 reason,并变为 rejected 状态
:::info Promise Resolution Procedure:Promise 决议程序是一个以输入为一个 promise 和 一个 value 的抽象操作,我们用promise2 = promise1.then(onFulfilled, onRejected);
[[Resolve]](promise, x)
来描述这一操作。如果x
是一个 thenable 对象,它会假设x
的行为有点像一个 promise 实例,并试图让promise
采用x
的状态。否则,它将以x
作为promise
的 value,并将promise
状态变为 fulfilled。
在执行 [[Resolve]](promise, x)
时,按照以下步骤执行
2.3.1 如果 promise
和 x
指向同一个对象,那么以 TypeError
作为理由拒绝 promise
2.3.2 如果 x
是一个 promise,采用它的状态
- 如果
x
是 pending 状态,promise
必须保留 pending 状态直到,x
状态变为 fulfilled/rejected - 如果
x
是 fulfilled 状态,将promise
状态变为 fulfilled,并返回相同的 value - 如果
x
是 rejeceted 状态,将promise
状态变为 rejected,并返回相同的 reason
2.3.3 否则,如果 x
是一个对象或函数
- 令
then
等于x.then
- 如果检索
x.then
这个属性而导致抛出异常e
,则将promise
状态变为 rejected,并以e
作为 reason - 如果
then
是一个函数,那么将它的this
指向x
,第一个参数为resolvePromise
,第二个参数为rejectPromise
- 如果
resolvePromie
以一个y
作为 value 被调用,则执行[[Resolve]](promise, y)
- 如果
rejectPromise
以一个r
作为 reason 被调用,以r
为理由返回 rejected 状态的promise
- 如果
resolvePromie
和rejectPromise
同时被调用了,或者对其中一个多次调用,那么以第一次调用为主,忽略之后的调用 - 如果调用
then
方法抛出异常e
- 如果
resolvePromie
和rejectPromise
被调用过了,忽略它 - 否则,以
e
为理由返回 rejected 状态的promise
- 如果
- 如果
- 如果
then
不是一个函数,则将promise
状态变为 fulfilled,并以x
为值
2.3.4 如果 x
不是对象或函数,则将promise
状态变为 fulfilled,并以 x
为值
:::
按照 Promise/A+ 规范的要求,我们先来实现 then 函数的一部分功能,第 2.2.1 ~ 2.2.6 点,测试用例如下
const promise1 = new MyPromise((resolve, reject) => {
reject(123)
})
promise1.then(
data => console.log(data, 1),
reason => {
console.log(reason + 'error1')
return 555
}
)
promise1.then(
'data',
reason => console.log(reason + 'error2')
)
class MyPromise {
// Promise 三个状态
static PENDING = 0
static FULLFILLED = 1
static REJECTED = 2
transition(state, value) {
// 状态 state 只能从 PENDING ——> FULLFILLED 或 PENDING ——> REJECTED
if (this.state === state
|| this.state === MyPromise.FULLFILLED
|| this.state === MyPromise.REJECTED) {
return false
}
this.state = state
this.value = value
return true
}
// 实现 resolve
resolve = (data) => {
// 状态变化: PENDING ——> FULLFILLED
const stateChange = this.transition(MyPromise.FULLFILLED, data)
if (stateChange) {
while (this.onFulfilledQueue.length) {
const onFulfilled = this.onFulfilledQueue.shift()
onFulfilled(this.value)
}
}
}
// 实现 reject
reject = (reason) => {
// 状态变化: PENDING ——> REJECTED
const stateChange = this.transition(MyPromise.REJECTED, reason)
if (stateChange) {
while (this.onRejectedQueue.length) {
const onRejected = this.onRejectedQueue.shift()
onRejected(this.value)
}
}
}
constructor(executor) {
this.state = MyPromise.PENDING
this.value = undefined
this.onFulfilledQueue = []
this.onRejectedQueue = []
try {
// executor 的执行时机需要比 then 晚,then 方法才能先注册 onFulfilled, onRejected 事件
// 这里使用 setTimeout 来将 executor 放入宏任务队列中等待执行
// 当主线程中的 then 方法执行完成后,再触发 executor
const timer = setTimeout(() => {
executor(this.resolve, this.reject)
clearTimeout(timer)
})
} catch (error) {
this.state = MyPromise.REJECTED
console.error(error)
}
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled === 'function') this.onFulfilledQueue.push(onFulfilled)
if (typeof onRejected === 'function') this.onRejectedQueue.push(onRejected)
}
}
:::warning ⭐注意:上面代码用了 setTimeout 来让 executor 执行时机比 then 晚,但这会导致两个问题。
- try catch 是同步的,不能捕获 executor 执行时抛出的错误。怎么解决我们下文再说。
- 原生 Promise 的 executor 是同步执行的,而我们实现的 executor 现在变成异步执行的 ::: 目前,由于 then 没有返回任何东西,如果我们像下面这样调用 then 函数的话,会有问题。 ```javascript const promise1 = new MyPromise((resolve, reject) => { reject(123) })
const promise2 = promise1.then( ‘data’, reason => reason + ‘error’ )
promise2.then( data => console.log(data), reason => console.error(reason) )
接下来,我们需要实现 Promise/A+ 规范的第 2.2.7 点。
> Promise 的 then 方法链式调用,为什么不能直接返回 this ?
> 答:根据 Promise/A+ 规范的第 2.2.2、2.2.3 点,`onFulFilled`和`onRejected`只能在 promise 实例状态由 pending 变为 fulfilled/rejected 时,才能被调用。直接返回 this,promise 状态不对
```javascript
class MyPromise {
// Promise 三个状态
static PENDING = 0
static FULLFILLED = 1
static REJECTED = 2
transition(state, value) {
// 状态 state 只能从 PENDING ——> FULLFILLED 或 PENDING ——> REJECTED
if (this.state === state
|| this.state === MyPromise.FULLFILLED
|| this.state === MyPromise.REJECTED) {
return false
}
this.state = state
this.value = value
return true
}
// 实现 resolve
resolve = (data) => {
// 状态变化: PENDING ——> FULLFILLED
const stateChange = this.transition(MyPromise.FULLFILLED, data)
if (stateChange) {
while (this.onFulfilledQueue.length) {
const onFulfilled = this.onFulfilledQueue.shift()
onFulfilled(this.value)
}
}
}
// 实现 reject
reject = (reason) => {
// 状态变化: PENDING ——> REJECTED
const stateChange = this.transition(MyPromise.REJECTED, reason)
if (stateChange) {
while (this.onRejectedQueue.length) {
const onRejected = this.onRejectedQueue.shift()
onRejected(this.value)
}
}
}
constructor(executor) {
this.state = MyPromise.PENDING
this.value = undefined
this.onFulfilledQueue = []
this.onRejectedQueue = []
try {
executor(this.resolve, this.reject)
} catch (error) {
this.state = MyPromise.REJECTED
this.reject(error)
}
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') onFulfilled = data => data
if (typeof onRejected !== 'function') onRejected = reason => { throw reason }
const promise1 = this
// return 的就是 promise2
const promise2 = new MyPromise((resolve, reject) => {
// 1. executor 执行时机要比 then 晚
// 2. then 中的回调函数执行时机要比 executor 晚
const onFulfilledTask = () => {
const timer = setTimeout(() => {
try {
const x = onFulfilled(promise1.value)
MyPromise.PromiseResolution(promise2, x, resolve, reject)
clearTimeout(timer)
} catch (error) {
reject(error)
clearTimeout(timer)
}
})
}
const onRejectedTask = () => {
const timer = setTimeout(() => {
try {
const x = onRejected(promise1.value)
MyPromise.PromiseResolution(promise2, x, resolve, reject)
clearTimeout(timer)
} catch (error) {
reject(error)
clearTimeout(timer)
}
})
}
try {
if (promise1.state === MyPromise.FULLFILLED) {
onFulfilledTask()
} else if (promise1.state === MyPromise.REJECTED) {
onRejectedTask()
} else if (promise1.state === MyPromise.PENDING) {
promise1.onFulfilledQueue.push(onFulfilledTask)
promise1.onRejectedQueue.push(onRejectedTask)
}
} catch (error) {
// 按照 Promise/A+ 的 2.2.7 的 b 点
// 不论是 onFulfilled 还是 onRejected 抛出一个异常 e,promise2 必须以 e 作为错误原因变为 rejected 状态
reject(error)
}
})
return promise2
}
// Promise Resolution Procedure
static PromiseResolution(promise2, x, resolve, reject) {
if (promise2 === x) reject(new TypeError())
else if (x === null) resolve(x)
// 不论 x 是 promise 实例,还是用户自定义 thenable 对象/函数,都执行如下逻辑
else if (['object', 'function'].includes(typeof x)) {
let then
try {
then = x.then
} catch (error) {
reject(error)
}
if (typeof then !== 'function') {
resolve(x)
} else {
// 根据 2.3.3 的 第 iii 点,使用 firstCall
// 使得只执行第一次 resolvePromise/rejectPromise 调用
let firstCall = true
try {
then.call(x,
// resolvePromise
(y) => {
if (!firstCall) return
firstCall = false
MyPromise.PromiseResolution(promise2, y, resolve, reject)
},
// rejectPromise
(r) => {
if (!firstCall) return
firstCall = false
reject(r)
})
}
catch (error) {
if (!firstCall) return
reject(error)
}
}
}
else {
resolve(x)
}
}
}
module.exports = MyPromise
测试 then 方法是否合乎标准
使用 promises-aplus-tests 库,测试手写的 Promise 是否合乎 Promise/A+ 规范
// 添加 deferred 方法,promises-aplus-tests 会去调用 MyPromise 执行测试用例
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
{
"name": "MyPromise",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "promises-aplus-tests index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
}
}
执行完 npm run test,看到类似下面情况,说明测试用例都通过了