写在前面
通过链式调用Promise的方式可以解决“回调地狱”的问题。本文主要解读Promise/A+规范,在此规范的基础上实现一个Promise。
Promise/A+ 规范
一个开放、健全且通用的 JavaScript Promise 标准。由开发者制定,供开发者参考。Promise/A+ 规范是业内所有的 Promise 类库的统一规范。
Promise/A+ 规范英文文档: promisesaplus.com/ Promise/A+ 规范(翻译)图灵译文: www.ituring.com.cn/article/665…
Promise的使用
创建一个promise实例:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
reject('fail')
}, 1000)
console.log('创建一个promise')
})
.then(
(value) => {
console.log('111')
},
(reason) => {
console.log('222')
}
)
.then((value) => {
console.log('333')
})
// 创建一个promise
// success
// 333
- new的Promise实例接受一个执行器函数(executor),该函数接受两个参数成功和失败的方法,执行器函数立即执行.
- 只输出resolve 的内容,reject 的内容没有输出,即 Promise 是有-状态且状态只可以由pending -> fulfilled或 pending-> rejected,是不可逆的
- Promise支持链式编程,也就是说then方法返回的也会是一个Promise实例.
实现自己的Promise
状态
都知道promise是有三种状态值的
- 等待态(Pending)
处于等待态时,Promise 需满足条件:
- 可以迁移至执行态或拒绝态
- 执行态(Fulfilled)
处于执行态时,promise 需满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的终值
- 拒绝态(Rejected)
处于拒绝态时,promise 需满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的据因
// Promise 三个状态
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败
class Promise {
constructor(executor) {
this.status = PENDING // 默认pending状态
this.value = undefined // 成功传入的值
this.reason = undefined // 失败传入的原因
// 成功 传入成功的值 status变为FULFILLED
let resolve = (value) => {
// 判断status 如果status改变就不会再被改变了,要么是成功态,要么是失败态
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
}
}
// 失败 失败传入的原因 status变为REJECTED
let reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
try {
executor(resolve, reject) // 默认执行器立即执行
} catch (e) {
reject(e) // 报错直接抛出异常
}
}
}
then()
一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。
- 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
- 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值
- promise 的 then 方法接受两个参数:
promise.then(onFulfilled, onRejected)
- onFulfilled 和 onRejected 都是可选参数
- then 方法可以被同一个 promise 调用多次,then 方法必须返回一个 promise 对象
then(onFulfilled, onRejected) {
// then接受两个参数成功的回调与失败的回调
// 参数可选
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: (data) => {
data
}
onRejected =
typeof onRejected === 'function'
? onRejected
: (error) => {
throw error
}
// then方法可以被同一个promise调用多次 必然是返回了一个新的Promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
let x = onFulfilled(this.value)
resolve(x)
}
if (this.status === REJECTED) {
let x = onRejected(this.reason)
reject(x)
}
})
// then方法必须返回一个promise对象
return promise2
}
到这里呢,一个简单的promise就已经完成了,但是我们都是处理同步的情况,那遇到异步的时候该怎么处理呢?我们继续往下写
实现异步的promise
我们先来看下面的代码是什么时候改变状态的
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('111111111')
//只要用户调用了resolve或者reject,那就表明Promise的状态发生了改变。
}, 1000)
}).then(
(res) => {
console.log('res', res)
},
(err) => {
console.log('err', err)
}
)
- 调用resolve或者reject,那就表明Promise的状态发生了改变,所以我们在状态改变后再去执行传入的回调,我们优化下构造器里面的resolve和reject方法
this.onFulfilledCbs = [] // 存放then成功回调的数组
this.onRejectedCbs = [] // 存放then失败回调的数组
// 成功 传入成功的值 status变为FULFILLED
let resolve = (value) => {
// 判断status 如果status改变就不会再被改变了,要么是成功态,要么是失败态
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 状态改变后依次执行
this.onFulfilledCbs.forEach((fn) => fn())
}
}
// 失败 失败传入的原因 status变为REJECTED
let reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 状态改变后依次执行
this.onRejectedCbs.forEach((fn) => fn())
}
}
在then函数中
- 如果是fulfilled或者rejected状态,直接执行回调函数
- 如果是pending状态,将回调函数入栈,等待状态改变之后再去执行传入的回调
- 如果onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e
then(onFulfilled, onRejected) {
// then接受两个参数成功的回调与失败的回调
// 参数可选
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: (data) => {
data
}
onRejected =
typeof onRejected === 'function'
? onRejected
: (error) => {
throw error
}
// then方法可以被链式调用 必然是返回了一个新的Promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 如果onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e
try {
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
// 抛出一个异常e,并返回拒因e
reject(e)
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason)
reject(x)
} catch (e) {
reject(e)
}
}
if (this.status === PENDING) {
// 如果是异步就先订阅好
this.onFulfilledCbs.push(() => {
try {
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
this.onRejectedCbs.push(() => {
try {
let x = onRejected(this.reason )
reject(x)
} catch (e) {
reject(e)
}
})
}
})
// then方法必须返回一个promise对象
return promise2
}
Promise 解决过程,resolvePromise函数
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x. This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant then method. It also allows Promises/A+ implementations to “assimilate” nonconformant implementations with reasonable then methods.
翻译过来大概是:
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为[[Resolve]](promise,x)
如果x是可能的,则在x的行为至少类似于承诺的假设下,尝试使承诺采用x的状态。 否则,它将以值x履行承诺。这种对可实现对象的处理使答应实现可以互操作,只要它们公开了符合Promises / A +的then方法即可。 它还允许Promises / A +实现使用合理的then方法“整合”不合格的实现。
Promise 解决过程
PromiseA+规定运行[[Resolve]](promise, x)
需遵循以下步骤:
- x 与 promise 相等 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
- x 为 Promise
- 如果 x 为 Promise ,则使 promise 接受 x 的状态:
- 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
- 如果 x 处于执行态,用相同的值执行 promise
- 如果 x 处于拒绝态,用相同的据因拒绝 promise
- 如果 x 为 Promise ,则使 promise 接受 x 的状态:
- x 为对象或函数
- 把 x.then 赋值给 then
- 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
- 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
- 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
- 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
- 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
- 如果调用 then 方法抛出了异常 e:
- 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
- 否则以 e 为据因拒绝 promise
- 如果 then 不是函数,以 x 为参数执行 promise
- 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolvePromise函数的实现
Promise解决过程抽离,用resolvePromise函数实现
then
方法必须返回一个promise
对象,我们记为promise2
, resolvePromise()
需要拿到当前的 promise
及其 resolve
reject
回调函数,resolvePromise()
是立即执行,使用setTimeout
延迟执行拿到 promise2
then
then(onFulfilled, onRejected) {
...
let promise2 = new Promise((resolve, reject) => {
...
if (this.status === FULFILLED) {
// 如果onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
...
})
...
// then方法必须返回一个promise对象
return promise2
}
resolvePromise
//resolvePromise的实现遵循PromiseA+规范
const resolvePromise = (promise2, x, resolve, reject) => {
// promise2的调用限制,防止多次调用成功和失败
// resolve和reject均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
let called
if (x === promise2) {
reject(
//如果promise和x指向同一对象,以TypeError为据因拒绝执行promise
new TypeError('Chaining cycle detected for Promise #<Promise>')
)
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// x为对象或函数
try {
// 把x.then赋值给then 能保证不能再用次取then的值
let then = x.then()
if (typeof then === 'function') {
// then如果是一个函数防止then方法可能会被Object.defineProperty定义,需要进行容错处理
// 直接调用then并以当前调用成功的resolve作为resolve抛出,如果失败则以当前失败的reject作为Promise失败的原因
then.call(
(y) => {
if (called) {
return
}
called = true
// y有可能还是一个Promise,递归调用resolvePromise直到解析出来的值是一个普通值/对象
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) {
return
}
called = true
reject(e)
}
)
} else {
// 如果x是一个普通对象让promise2成功
resolve(x)
}
} catch (e) {
if (called) {
return
}
called = true
// 抛出异常则采用失败结果向下传递
reject(e)
}
} else {
// x是一个普通值直接让promise2成功即可
resolve(x)
}
}
总结:
- 如果then中无论是成功、失败有return返回值,都会走下一个then的成功
- 如果第一个promise返回了一个普通值,会进入下一次then的成功回调;如果第一个promise返回了一个新的promise,需要等待返回promise执行的结果传递给下一次的then中
到此符合 Promise A+ 规范的Promise基本已经完成啦!
defer
defer的作用:
- 它不需要new来生成promise实例,优化了promise嵌套的问题
- 测试库promises-aplus-tests需要写这么一个方法🙈
defer的实现
// 扩充 api
// 解决嵌套
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
defer的使用:
function a() {
let defer = Promise.defer()
defer.resolve('success')
// defer.reject('fail')
return defer.promise
}
a().then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
.then((value) => {
console.log(value)
})
Promise 规范测试
npm install promises-aplus-tests -g
进入到Promise.js所在目录,运行
promises-aplus-tests ./Promise.js
测试结果
扩展Promise 5 个静态方法、2 个实例方法
静态方法
resolve reject all allSettled race
Promise.resolve
Promise.resolve('foo')
等价于new Promise(resolve => resolve('foo'))
Promise.resolve = function (value) {
// value如果是promise的话不能直接返回,需要调用then
if (value instanceof Promise) {
try {
let then = value.then
return new Promise(then.bind(value))
} catch (error) {
return new Promise((resolve, reject) => {
reject(error)
})
}
} else {
return new Promise((resolve, reject) => {
resolve(value)
})
}
}
Promise.reject
Promise.reject = (reason) => {
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise.all
Promise.all 接收一个Promise实例的数组,传入的Promise实例都fulfilled后,按照Promise实例传入的顺序返回相应结果的数组,如果其中有一个rejected则返回最先被reject失败状态的值
Promise.all = function (values) {
return new Promise((resolve, reject) => {
let arr = []
let i = 0
const valueNum = values.length
function resolveData(data, index) {
arr[index] = data
if (++i === valueNum) {
resolve(arr)
}
}
values.forEach((value, index) => {
try {
if (value instanceof Promise) {
// 判断数组中的值是不是一个promise
// yes: 成功状态 =》拿到它的值push到相应结果的数组
// 失败状态=》直接reject失败的原因
// no: 当普通值处理 push到相应结果的数组
value.then(
(res) => {
resolveData(res, index)
},
(err) => {
reject(err)
}
)
} else {
resolveData(value, index)
}
} catch (error) {
reject(error)
}
})
})
}
Promise.all的使用
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('111'), 0)
})
const p2 = new Promise((resolve, reject) => {
// setTimeout(() => reject('222'), 0)
setTimeout(() => resolve('222'), 0)
})
Promise.all([p1, p2, '123']).then(
(value) => {
console.log(value) // ['111','222','123']
},
(err) => {
console.log(err) // 222 (p2的注释打开输出这里)
}
)
Promise.allSettled
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果,对于每个结果对象,都有一个status字符串。如果它的值为fulfilled,则结果对象上存在一个value;如果值为rejected,则存在一个reason。value/reason反映了每个promise决议/拒绝的值。
Promise.allSettled = (values) => {
const result = values.map((p) => {
return Promise.resolve(p).then(
(value) => {
return { status: 'fulfilled', value }
},
(reason) => {
return { status: 'rejected', reason }
}
)
})
return Promise.all(result)
}
Promise.allSettled的使用
const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, 'foo')
)
const promises = [promise1, promise2]
Promise.allSettled(promises).then((results) => {
results.forEach((result) => console.log(result))
})
// { status: 'fulfilled', value: 3 }
// { status: 'rejected', reason: 'foo' }
Promise.race
Promise.race方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。就是哪个结果获得的快,就返回哪个结果,不管结果本身是成功状态还是失败状态。
Promise.race = (values) => {
return new Promise((resolve, reject) => {
values.forEach((p) => {
p.then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
})
})
}
Promise.race的使用
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one')
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two')
})
Promise.race([promise1, promise2]).then((value) => {
console.log(value)
// one
})
实例方法
catch finally
catch
catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同
catch(fn) {
return this.then(undefined, fn)
}
catch的使用
var p1 = new Promise(function (resolve, reject) {
resolve('Success')
})
p1.then(function (value) {
console.log(value) // "Success!"
throw 'oh, no!'
})
.catch(function (e) {
console.log(e) // "oh, no!"
})
.then(
function () {
console.log('after a catch the chain is restored') // after a catch the chain is restored
},
function () {
console.log('Not fired due to the catch')
}
)
finally
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式,避免了同样的语句需要在then()和catch()中各写一次的情况。
finally(fn) {
return this.then(
(data) => {
return Promise.resolve(fn()).then(() => {
data
})
},
(err) => {
return Promise.resolve(fn()).then(() => {
throw err
})
}
)
}
finally的使用
var promise = new Promise(function (resolve, reject) {
resolve('error')
})
.then(() => {
console.log('success') // success
})
.catch(() => {
console.log('catch')
})
.finally(() => {
console.log('finally') // finally
})
好了,我们的Promise到这里就写完了,如果你有其他的补充或者觉得我有什么地方说得不对的话可以在评论区和我一起探讨。😁
源码献上 ❤️
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败
//resolvePromise的实现遵循Promise+规范
const resolvePromise = (promise2, x, resolve, reject) => {
// promise2的调用限制,防止多次调用成功和失败
// resolve和reject均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
let called
if (x === promise2) {
reject(
//如果promise和x指向同一对象,以TypeError为据因拒绝执行promise
new TypeError('Chaining cycle detected for Promise #<Promise>')
)
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// x为对象或函数
try {
// 把x.then赋值给then 能保证不能再用次取then的值
let then = x.then()
if (typeof then === 'function') {
// then如果是一个函数防止then方法可能会被Object.defineProperty定义,需要进行容错处理
// 直接调用then并以当前调用成功的resolve作为resolve抛出,如果失败则以当前失败的reject作为Promise失败的原因
then.call(
(y) => {
if (called) {
return
}
called = true
// y有可能还是一个Promise,递归调用resolvePromise直到解析出来的值是一个普通值/对象
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) {
return
}
called = true
reject(r)
}
)
} else {
// 如果x是一个普通对象让promise2成功
resolve(x)
}
} catch (e) {
if (called) {
return
}
called = true
// 抛出异常则采用失败结果向下传递
reject(e)
}
} else {
// x是一个普通值直接让promise2成功即可
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = PENDING // 默认pending状态
this.value = undefined // 成功传入的值
this.reason = undefined // 失败传入的原因
this.onFulfilledCbs = [] // 存放then成功回调的数组
this.onRejectedCbs = [] // 存放then失败回调的数组
// 成功 传入成功的值 status变为FULFILLED
let resolve = (value) => {
// 判断status 如果status改变就不会再被改变了,要么是成功态,要么是失败态
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 状态改变后依次执行
this.onFulfilledCbs.forEach((fn) => fn())
}
}
// 失败 失败传入的原因 status变为REJECTED
let reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 状态改变后依次执行
this.onRejectedCbs.forEach((fn) => fn())
}
}
try {
executor(resolve, reject) // 默认执行器立即执行
} catch (e) {
reject(e) // 报错直接抛出异常
}
}
then(onFulfilled, onRejected) {
// then接受两个参数成功的回调与失败的回调
// 参数可选
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: (data) => {
data
}
onRejected =
typeof onRejected === 'function'
? onRejected
: (error) => {
throw error
}
// then方法可以被同一个promise调用多次 必然是返回了一个新的Promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 如果onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
try {
// let x = onRejected(this.value)
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
// then方法必须返回一个promise对象
return promise2
}
catch(fn) {
return this.then(undefined, fn)
}
finally(fn) {
return this.then(
(data) => {
return Promise.resolve(fn()).then(() => {
data
})
},
(err) => {
return Promise.resolve(fn()).then(() => {
throw err
})
}
)
}
}
// 扩充 api
// 解决嵌套
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
Promise.resolve = function (value) {
if (value instanceof Promise) {
try {
let then = value.then
return new Promise(then.bind(value))
} catch (error) {
return new Promise((resolve, reject) => {
reject(error)
})
}
} else {
return new Promise((resolve, reject) => {
resolve(value)
})
}
}
Promise.reject = (reason) => {
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise.all = function (values) {
return new Promise((resolve, reject) => {
let arr = []
let i = 0
const valueNum = values.length
function resolveData(data, index) {
arr[index] = data
if (++i === valueNum) {
resolve(arr)
}
}
values.forEach((value, index) => {
try {
if (value instanceof Promise) {
// 判断数组中的值是不是一个promise
// yes: 成功状态 =》拿到它的值push到相应结果的数组
// 失败状态=》直接reject失败的原因
// no: 当普通值处理 push到相应结果的数组
value.then(
(res) => {
resolveData(res, index)
},
(err) => {
reject(err)
}
)
} else {
resolveData(value, index)
}
} catch (err) {
reject(err)
}
})
})
}
Promise.allSettled = (values) => {
const result = values.map((p) => {
return Promise.resolve(p).then(
(value) => {
return { status: 'fulfilled', value }
},
(reason) => {
return { status: 'rejected', reason }
}
)
})
return Promise.all(result)
}
Promise.race = (values) => {
return new Promise((resolve, reject) => {
values.forEach((p) => {
p.then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
})
})
}
module.exports = Promise