Promise系列文章
上一篇已经通过一些文档和例子,简单地了解了Promise的API使用。本篇我们根据上一篇中总结的一些协议要点,用JavaScript简单实现符合Promise A+协议的程序。
源码编写
实现一个同步Promise框架
- Promise 是一个类,拥有三种状态(pending,fulfilled,rejected)
- Promise 构造函数初始化的时候,需要传入一个默认会执行的函数 executor,需要我们传入两个函数参数 resolve/reject
- Promise 默认状态为 Pending 等待态
- 当客户调用 executor 参数 resolve/reject 时候会修改当前状态,并且记录成功值(value)/拒绝原因(reason)
- 只能修改 pending 状态到 fulfilled/rejected,如果是 fulfilled/rejected 态下无法修改当前状态
- 每一个 Priomise 都必须提供一个 then 方法,默认传入两个回调方法,程序控制,当 fulfilled 态的时候默认调用 onFulfilled,当 rejected 态的时候,调用 onRejected 方法
// promise 拥有三种状态 等待态,成功态,拒绝态
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
}
class Promise {
constructor(executor) {
// promise默认状态是Pending
this.status = STATUS.PENDING
this.value = undefined // 存储成功值,方便后面链式调用
this.reason = undefined // 存储失败的原因,方便后面链式调用
// 用户在执行函数到正确完成得时候,会调用resolve函数来修改当前实例的状态,resolve函数支持传入一个参数,作为成功的值
const resolve = (value) => {
// 只有在等待态下才能修改promise状态
if (this.status === STATUS.PENDING) {
// 修改实例的状态,为成功态
this.status = STATUS.FULFILLED
// 接受用户传来的成功值
this.value = value
}
}
// 用户在执行函数得到错误的内容或者抛出异常的时候,会调用reject函数来修改当前实例的状态,reject函数支持传入一个参数,作为失败/拒绝的原因
const reject = (reason) => {
// 只有等待态下才能修改promise
if (this.status === STATUS.PENDING) {
// 修改实例的状态为失败态
this.status = STATUS.REJECTED
// 接受用户传来的失败原因
this.reason = reason
}
}
try {
// new Promise() 会传入一个默认执行的函数,并且函数接受两个可执行的方法参数
executor(resolve, reject)
} catch (error) {
// 如果默认执行的函数执行报错,则直接将状态设置为拒绝
reject(error)
}
}
/**
* 1. 每一个Priomise都必须提供一个then方法,默认传入两个回调方法,程序控制,当fulfilled态的时候默认调用onFulfilled,当rejected态的时候,调用onRejected方法
* @param {*} onFulfilled 成功执行函数
* @param {*} onRejected 失败执行函数
*/
then(onFulfilled, onRejected) {
if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
}
if (this.status === STATUS.FULFILLED) {
onRejected(this.reason)
}
}
}
module.exports = Promise
我们实现了一个 Promise 基础架子,但是作为一个主要是处理异步操作的而存在的类,这个时候如果我们加入异步,并不能实现功能。那下面我们将异步功能加入
实现Promise的异步调用
如果 executor 方法是异步实现,我们在 then 中判断 STATUS 为 pending 状态的时候,将 onFulfilled 和 onRejected 方法保存起来,等到修改状态的时候,将保存起来的方法调用。
constructor(executor){
// ...
// 如果在状态未变情况下,调用then的时候,我们需要将then中成功/失败方法存储,等到状态变化方法主动调用
this.onFulfilledCallbacks = [] // then的成功方法集
this.onRejectedCallbacks = [] // then的失败方法集
const resolve = (val) => {
if(this.status === STATUS.PENDDING) {
// ...
// 状态变化之后,发起then的成功方法集调用,实现异步操作
this.onFulfilledCallbacks.forEach((fn) => fn())
}
}
const reject = (reason) => {
if(this.status === STATUS.PENDDING) {
...
// 状态变化之后,发起then的失败方法集调用,实现异步操作
this.onRejectedCallbacks.forEach((fn) => fn())
}
}
// ...
}
then(onFulfilled,onReject) {
// ...
// 当promise的状态为PENDING,这个时候属于异步操作
// 通过发布订阅模式,将resolve和reject方法发布,等到后续状态变化完成之后,发起调用
if (this.status === STATUS.PENDING) {
// 使用装饰器模式/切片编程将成功/失败方法包装,方便我们做其他操作,以及获取状态变化后得 value/reason
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
// ...
到了这一步,我们基本实现了简单地一个 Promise 单 then 的方法调用。 但是 Promise 的最主要提高满意度的 then 链式调用还不能使用,下面我们来加入链式调用
then()链式调用深入实现
- 每一个 Promise 的原型上面都有一个 then 方法,并且 then 方法会提供两个可执行方法参数
- then 方法必须返回一个 promise,我们通过 new 一个新的 Promise 达到效果
- then 在链式调用的时候,后续 then 的调用规则
- 如果在调用当前成功/失败的方法时,如果抛出异常,会走入下一次 then 的失败方法中
- 获取到成功/失败的返回值 x
- 如果 x 是基础数据类型,会走入下一次 then 的成功方法中(无论成功还是失败都会调用 then 的成功方法)
- 如果 x 是对象
- x 是普通对象,直接走入下一个 then 的成功方法中
- x 是 Promise,获取到 x 的 then,继续调用 then,知道成功获取到普通对象或者基础数据类型
- 在 promise2 的 then 的成功和失败方法调用 promise 对应的 resolve/reject 的方法
- 下一次的 then 的成功还是失败是由 x 的成功还是失败决定
- then 的后续链式调用,只有在本次抛出异常,或回调方法返回的 Promise 失败的时候才会走入下一次的失败中,否则都是走下次 resolve 方法
then(onFulfilled, onRejected) {
// 创建一个新的promise2,并且在方法最后返回,是then达到链式调用,继续then其实是调用新new出来的promise实例
const promise2 = new Promise((resolve, reject) => {
// 当我们调用then方法的时候,通过判断当前Promise状态,调用不同的传参方法
// 当promise的状态为Fulfilled的时候,调用onFulfilled方法,并且将成功值作为参数传入
if (this.status === STATUS.FULFILLED) {
// 此处需要处理下promise2和x的情况,但是promise2是在new之后赋值的,所以需要使用一个异步宏/微来达到能取到promise
setTimeout(() => {
try {
const x = onFulfilled(this.value)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 当promise的状态为REJECTED的时候,调用onRejected方法,并且将失败原因作为参数传入
if (this.status === STATUS.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 当promise的状态为PENDING,这个时候属于异步操作
// 通过发布订阅模式,将resolve和reject方法发布,等到后续状态变化完成之后,发起调用
if (this.status === STATUS.PENDING) {
// 使用装饰器模式/切片编程将成功/失败方法包装,方便我们做其他操作,以及获取状态变化后得 value/reason
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
}
在各种状态下对应成功失败回调的方法处理都是一样的,所以提供一个工具方法统一使用
// 统一处理then中返回的Promise(promise2)的后续变化状态的操作
function resolvePromise(x, promise2, resolve, reject) {
// 如果promise2 就是自己传入的
if (x === promise2) {
return reject(new TypeError('重复调用'))
}
// 判断传入的x是否是对象或者方法
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 为了兼容其他人的Promise, 防止其他promise重复调用加一个防重
let called
// 如果抛出异常,就reject(x)
try {
// 获取到x的then属性
const then = x.then
// 如果then是function,我们就默认x是Promise
if (typeof then === 'function') {
// 调用x的then方法,如果x内部成功/失败调用对应的成功和失败
then.call(
x,
(y) => {
if (called) return
called = true
// 如果y还是Promise 需要再次解析then,知道不是Promise
// resolve(y)
resolvePromise(y, promise2, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} else {
// 如果then不是函数,我们默认这个x是一个非Promise对象,直接resolve(x)
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
// 如果传入的值是一个原始值类型,直接resolve(x)
resolve(x)
}
}
处理 then 的空参数问题
当我们在使用 Promise 的时候,需要注意下使用者可能会写出 new Promise((resolve,reject) => {resolve(1)}).then().then().then(onFulfilled => {})这种代码。 我们处理这种情况下。
then(onFulfilled, onRejected) {
// 如果onFulfilled/onRejected 无法获取到需要将当前的成功/失败值传给下一个then
// new promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) 需要能够将of值传入
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data
onRejected = typeof onRejected === 'function' ? onRejected : e => {throw e}
// ...
}
处理 resolve 方法传入一个 Promise
如果使用者在构造函数初始化的时候,传入一个 Promise,我们需要使用这个 Promise 的成功或者失败作为我们本次 Promise 的成功/失败
constructor(executor) {
//...
const resolve = (value) => {
// 判断传入的值是否是Promise, 如果还是Promise,那么直接返回then的调用,将resolve/reject传入then的成功和失败回调,如果Promise成功/失败,通过回调参数,修改了当前Promise的状态
if (value instanceof Promise) {
return value.then(resolve, reject)
}
//。。。
}
}
测试
通过 Promise A+ 链接找到 promises-aplus-tests,对本案例做测试,如果成功就代表我们的 Promise 符合协议标准,可以混合使用
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
最终代码
// promise 拥有三种状态 等待态,成功态,拒绝态
const STATUS = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED',
}
class Promise {
constructor(executor) {
// promise默认状态是Pending
this.status = STATUS.PENDING
this.value = undefined // 存储成功值,方便后面链式调用
this.reason = undefined // 存储失败的原因,方便后面链式调用
// 如果在状态未变情况下,调用then的时候,我们需要将then中成功/失败方法存储,等到状态变化方法主动调用
this.onFulfilledCallbacks = [] // then的成功方法集
this.onRejectedCallbacks = [] // then的失败方法集
// 用户在执行函数到正确完成得时候,会调用resolve函数来修改当前实例的状态,resolve函数支持传入一个参数,作为成功的值
const resolve = (value) => {
// 判断传入的值是否是Promise, 如果还是Promise,那么直接返回then的调用,将resolve/reject传入then的成功和失败回调,如果Promise成功/失败,通过回调参数,修改了当前Promise的状态
if (value instanceof Promise) {
return value.then(resolve, reject)
}
// 只有在等待态下才能修改promise状态
if (this.status === STATUS.PENDING) {
// 修改实例的状态,为成功态
this.status = STATUS.FULFILLED
// 接受用户传来的成功值
this.value = value
// 状态变化之后,发起then的成功方法集调用,实现异步操作
this.onFulfilledCallbacks.forEach((fn) => fn())
}
}
// 用户在执行函数得到错误的内容或者抛出异常的时候,会调用reject函数来修改当前实例的状态,reject函数支持传入一个参数,作为失败/拒绝的原因
const reject = (reason) => {
// 只有等待态下才能修改promise
if (this.status === STATUS.PENDING) {
// 修改实例的状态为失败态
this.status = STATUS.REJECTED
// 接受用户传来的失败原因
this.reason = reason
// 状态变化之后,发起then的成功方法集调用,实现异步操作
this.onRejectedCallbacks.forEach((fn) => fn())
}
}
try {
// new Promise() 会传入一个默认执行的函数,并且函数接受两个可执行的方法参数
executor(resolve, reject)
} catch (error) {
// 如果默认执行的函数执行报错,则直接将状态设置为拒绝
reject(error)
}
}
/**
* 1. 每一个Promise的原型上面都有一个then方法,并且then方法会提供两个可执行方法参数
* @param {*} onFulfilled 成功执行函数
* @param {*} onRejected 失败执行函数
* 2. then方法必须返回一个promise,我们通过new一个新的Promise达到效果
* @returns new Promise()
* 3. then在链式调用的时候,后续then的调用规则
* + 如果在调用当前成功/失败的方法时,如果抛出异常,会走入下一次then的失败方法中
* + 获取到成功/失败的返回值x
* + 如果x是对象
* + x是普通对象,直接走入下一个then的成功方法中
* + x是Promise,获取到x的then,继续调用then,知道成功获取到普通对象或者基础数据类型
* + 在promise2的then的成功和失败方法调用promise对应的 resolve/reject的方法
* + 下一次的then的成功还是失败是由x的成功还是失败决定
* + 如果x是基础数据类型,会走入下一次then的成功方法中(无论成功还是失败都会调用then的成功方法)
* 4.then的后续链式调用,只有在本次抛出异常,或回调方法返回的Promise失败的时候才会走入下一次的失败中,否则都是走下次resolve方法
*
*/
then(onFulfilled, onRejected) {
// 如果onFulfilled/onRejected 无法获取到需要将当前的成功/失败值传给下一个then
// new promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) 需要能够将of值传入
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (data) => data
onRejected =
typeof onRejected === 'function'
? onRejected
: (e) => {
throw e
}
// 创建一个新的promise2,并且在方法最后返回,是then达到链式调用,继续then其实是调用新new出来的promise实例
const promise2 = new Promise((resolve, reject) => {
// 当我们调用then方法的时候,通过判断当前Promise状态,调用不同的传参方法
// 当promise的状态为Fulfilled的时候,调用onFulfilled方法,并且将成功值作为参数传入
if (this.status === STATUS.FULFILLED) {
// 此处需要处理下promise2和x的情况,但是promise2是在new之后赋值的,所以需要使用一个异步宏/微来达到能取到promise
setTimeout(() => {
try {
const x = onFulfilled(this.value)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 当promise的状态为REJECTED的时候,调用onRejected方法,并且将失败原因作为参数传入
if (this.status === STATUS.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
// 当promise的状态为PENDING,这个时候属于异步操作
// 通过发布订阅模式,将resolve和reject方法发布,等到后续状态变化完成之后,发起调用
if (this.status === STATUS.PENDING) {
// 使用装饰器模式/切片编程将成功/失败方法包装,方便我们做其他操作,以及获取状态变化后得 value/reason
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
// 我们使用一个外部方法来统一处理此处
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
}
// 统一处理then中返回的Promise(promise2)的后续变化状态的操作
function resolvePromise(x, promise2, resolve, reject) {
// 如果promise2 就是自己传入的
if (x === promise2) {
return reject(new TypeError('重复调用'))
}
// 判断传入的x是否是对象或者方法
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 为了兼容其他人的Promise, 防止其他promise重复调用加一个防重
let called
// 如果抛出异常,就reject(x)
try {
// 获取到x的then属性
const then = x.then
// 如果then是function,我们就默认x是Promise
if (typeof then === 'function') {
// 调用x的then方法,如果x内部成功/失败调用对应的成功和失败
then.call(
x,
(y) => {
if (called) return
called = true
// 如果y还是Promise 需要再次解析then,知道不是Promise
// resolve(y)
resolvePromise(y, promise2, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} else {
// 如果then不是函数,我们默认这个x是一个非Promise对象,直接resolve(x)
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
// 如果传入的值是一个原始值类型,直接resolve(x)
resolve(x)
}
}
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise