说明
以下是学习和使用Promise的过程中的一些总结,如有错误,请各位大佬批评指正?
原生Promise总结
要想自己手动实现一个Promise,首先肯定要充分了解原生的Promise为我们提供了那些功能,以及使用过程中那些该注意的点。
下面我们来简单的总结以下Promise的特点。通过它的一些原生用法一点一点的实现一个自己的MyPromise。
- Promise有三种状态,pending(进行中),fulfilled(已成功),rejected(已失败)。
- Promise的状态一旦改变就会凝固,不会发生改变,且只能由pending变为其他的状态。
- promise是一个构造函数,接收一个执行器函数,这个执行器函数接受两个参数用于改变Promise的状态。
- resolve方法用于将Promise的状态改变为成功,并且可以传递一个参数作为成功的值,reject方法用于将Promise的状态更改为失败,也可以传递一个参数做诶失败的原因。
实现一个最简单的Promise
那我们就先根据这些特点先创建一个最基础版本的MyPromise。
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { // Promise接受一个执行器函数并且立即执行它 constructor(executor) { executor(this.resolve, this.reject) } status = PENDING // 定义初始状态 value = undefined // 定义一个初始的成功的值 reason = undefined // 定义一个初始的失败的原因 resolve = (value) => { /* 一旦Promised的状态已经改变,直接return,否则将Promise的状态更改为成功,并且保存resolve函数传递 的值 */ if (this.status !== PENDING) return this.status = FULFILLED this.value = value } reject = (reason) => { /* 一旦Promised的状态已经改变,直接return,否则将Promise的状态更改为失败,并且保存reject函数传递的 失败的原因 */ if (this.status !== PENDING) return this.status = REJECTED this.reason = reason } }
then方法的实现
好了最基础版本的MyPromise已经完成了,接下来我们就要看Promise在状态改变之后要干些什么了。
- Promise提供了then方法用于处理Promise状态改变之后的值。
- then方法接收两个回调函数,分别用于处理成功之后的回调和失败之后的回调。
then(successCallback, failCallback) { if (this.status === FULFILLED) { successCallback(this.value) } else if (this.status === REJECTED) { failCallback(this.reason) } } const p1 = new MyPromise((resolve, reject) => { resolve(100) }) p1.then((value) => { console.log(value) }) // 输出100
看起来好像没啥问题,但是当我们吧代码稍微改一下就出问题了
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(100) },1000) }) p1.then((value) => { console.log(value) }) // 没有任何输出
定义then方法的时候只考虑它状态改变之后的情况了,如果出现异步操作的话then回调函数执行的时候状态还没改变呢?
class MyPromise { // Promise接受一个执行器函数并且立即执行它 constructor(executor) { executor(this.resolve, this.reject) } status = PENDING // 定义初始状态 value = undefined // 定义一个初始的成功的值 reason = undefined // 定义一个初始的失败的原因 successCallbacks = [] // 定义一个数组储存所有的成功回调 failCallbacks = [] // 定义一个数组储存所有的失败回调 resolve = (value) => { /* 一旦Promised的状态已经改变,直接return,否则将Promise的状态更改为成功,并且保存resolve函数传递 的值 */ if (this.status !== PENDING) return this.status = FULFILLED this.value = value /* 状态改变了,依次取出回调函数数组里面的值一一执行 */ while (this.successCallbacks.length) this.successCallbacks.shift()(this.value) } reject = (reason) => { /* 一旦Promised的状态已经改变,直接return,否则将Promise的状态更改为失败,并且保存reject函数传递的 失败的原因 */ if (this.status !== PENDING) return this.status = REJECTED this.reason = reason /* 状态改变了,依次取出回调函数数组里面的值一一执行 */ while (this.failCallbacks.length) this.failCallbacks.shift()(this.reason) } then(successCallback, failCallback) { if (this.status === FULFILLED) { successCallback(this.value) } else if (this.status === REJECTED) { failCallback(this.reason) } else { /* 调用then的时候如果状态还未改变,先将回调回调函数保存起来,等待状态改变之后再调用 */ this.successCallbacks.push(successCallback) this.failCallbacks.push(failCallback) } } } // 测试一下 const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }) p1.then((value) => { console.log(value) }) p1.then((value) => { console.log(value) }) // 1秒后输出两个100
then方法链式调用实现
异步操作的问题解决了,接下来又有一个问题了,then方法是可以链式调用的,说明then方法也是返回了一个Promise,而且上一个then方法的返回值是可以传递给下一个then方法的。
那我们就可以这么操作。
then(successCallback, failCallback) { // 创建一个新的Promnise对象返回,让then方法可以链式调用 const promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ let result = successCallback(this.value) resolve(result) } else if (this.status === REJECTED) { let result = failCallback(this.reason) reject(result) } else { /* 调用then的时候如果状态还未改变,先将回调回调函数保存起来,等待状态改变之后再调用 */ this.successCallbacks.push(successCallback) this.failCallbacks.push(failCallback) } }) return promise2 } const p1 = new MyPromise((resolve, reject) => { // setTimeout(() => { // resolve(100) // }, 1000) resolve(100) }) p1.then((value) => { console.log(value) return 'p1 success' }).then((value) => console.log(value)) // 100 // p1 success // 测试了一下确实没有问题
链式调用好像是解决了,突然又发现新的问题了,then方法不仅可以返回普通的值,还可以返回一个新的Promise对象,这种情况下我们就要判断下这个then方法返回的Promise对象返回的结果,如果他返回的是成功,那我们就要调用resolve将它的状态传递给下一个then放的成功回调,如果是失败的话,就传递给下一个then的失败回调。
then(successCallback, failCallback) { // 创建一个新的Promnise对象返回,让then方法可以链式调用 const promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ let result = successCallback(this.value) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 */ if (result instanceof MyPromise) { result.then( (value) => resolve(value), (reason) => reject(reason) ) } else { resolve(result) } } else if (this.status === REJECTED) { let result = failCallback(this.reason) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 */ if (result instanceof MyPromise) { result.then( (value) => resolve(value), (reason) => reject(reason) ) } else { resolve(result) } } else { /* 调用then的时候如果状态还未改变,先将回调回调函数保存起来,等待状态改变之后再调用 */ this.successCallbacks.push(successCallback) this.failCallbacks.push(failCallback) } }) return promise2 } const p1 = new MyPromise((resolve, reject) => { resolve(100) }) const p2 = new MyPromise((resolve, reject) => { reject('p2 rejected') }) p1.then((value) => { console.log(value) return p2 }).then( (value) => console.log(value), (reason) => console.log(reason) ) // 100 // p2 rejected
then方法循环调用处理
说道then的链式调用就不得不提到一点,then方法虽然可以返回一个Promise,但是它是不能返回它自身这个Promise的,这样就会导致一个循环调用的问题。我们可以看下原生Promise对于这种情况的处理。
// 重复操作单独定义一个方法 function resolvePromise(promise2, result, resolve, reject) { // 如果返回的Promise处理的结果就是promise2,直接抛出异常 if (promise2 === result) { return reject(new TypeError('循环引用啦!')) } if (result instanceof MyPromise) { result.then( (value) => resolve(value), (reason) => reject(reason) ) } else { resolve(result) } } then(successCallback, failCallback) { // 创建一个新的Promnise对象返回,让then方法可以链式调用 const promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ let result = successCallback(this.value) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 判断循环调用其实就是判断result和promise2是否相等,由于promise2没有执行完成的时候没有办法将它 作为参数传递,所以这里用了一个异步的方式 */ resolvePromise(promise2, result, resolve, reject) }, 0) } else if (this.status === REJECTED) { setTimeout(() => { let result = failCallback(this.reason) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 */ resolvePromise(promise2, result, resolve, reject) }, 0) } else { /* 调用then的时候如果状态还未改变,先将回调回调函数保存起来,等待状态改变之后再调用 */ this.successCallbacks.push(successCallback) this.failCallbacks.push(failCallback) } }) return promise2 } const p1 = new MyPromise((resolve, reject) => { resolve(100) }) let p3 = p1.then((value) => { console.log(value) return p3 }) p3.then( (value) => console.log(value), (reason) => { console.log(reason) } ) // 100 // 循环引用啦!
then方法忽略参数以及异常抛出
最后还有一点,then方法的参数是可以忽略的,当then不传参数的时候,默认会将上一个成功回调后的值或者失败之后的原因向下一个then方法传递,这里我们对then的参数做下处理就好了。
successCallback ? successCallback : value => value failCallback ? failCallback : reason => { throw reason}
最后加一下异常处理,使得执行器函数执行过程和then回调函数执行过程中抛出的异常都可以通过下一个then方法的第二个回调参数接收就差不多啦,再优化一下实现的方式。
整个代码贴在下面
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { /* Promise接受一个执行器函数并且立即执行它 */ constructor(executor) { // 如果执行器函数发生异常,直接执行reject try { executor(this.resolve, this.reject) } catch (e) { this.reject(e) } } status = PENDING // 定义初始状态 value = undefined // 定义一个初始的成功的值 reason = undefined // 定义一个初始的失败的原因 successCallbacks = [] // 定义一个数组储存所有的成功回调 failCallbacks = [] // 定义一个数组储存所有的失败回调 resolve = (value) => { /* 一旦Promised的状态已经改变,直接return,否则将Promise的状态更改为成功,并且保存resolve函数传递的值 */ if (this.status !== PENDING) return this.status = FULFILLED this.value = value /* 状态改变了,依次取出回调函数数组里面的值一一执行 */ while (this.successCallbacks.length) this.successCallbacks.shift()() } reject = (reason) => { /* 一旦Promised的状态已经改变,直接return,否则将Promise的状态更改为失败,并且保存reject函数传递的失败的原因 */ if (this.status !== PENDING) return this.status = REJECTED this.reason = reason /* 状态改变了,依次取出回调函数数组里面的值一一执行 */ while (this.failCallbacks.length) this.failCallbacks.shift()() } then(successCallback, failCallback) { // 如果then方法的参数没有传递我们就给他一个默认的方法使得成功的值或者失败的原因继续向下一个then传递 successCallback ? successCallback : (value) => value failCallback ? failCallback : (reason) => { throw reason } // 创建一个新的Promnise对象返回,让then方法可以链式调用 const promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ try { // 如果成功回调执行过程中出现异常,直接抛出给下一个then的回调 let result = successCallback(this.value) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 处理循环调用其实就是判断result和promise2是否相等,由于promise2没有执行完成的时候没有办法将它作为参数传递,所以这里用了一个异步的方式 */ resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }, 0) } else if (this.status === REJECTED) { setTimeout(() => { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ try { // 如果成功回调执行过程中出现异常,直接抛出给下一个then的回调 let result = failCallback(this.reason) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 处理循环调用其实就是判断result和promise2是否相等,由于promise2没有执行完成的时候没有办法将它作为参数传递,所以这里用了一个异步的方式 */ resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }, 0) } else { /* 调用then的时候如果状态还未改变,先将回调回调函数保存起来,等待状态改变之后再调用 */ this.successCallbacks.push(() => { setTimeout(() => { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ try { // 如果成功回调执行过程中出现异常,直接抛出给下一个then的回调 let result = successCallback(this.value) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 处理循环调用其实就是判断result和promise2是否相等,由于promise2没有执行完成的时候没有办法将它作为参数传递,所以这里用了一个异步的方式 */ resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }, 0) }) this.failCallbacks.push(() => { setTimeout(() => { /* 上一个then方法的回调函数执行后的返回值可以作为参数传递给下一个链试调用的then方法的回调 */ try { // 如果成功回调执行过程中出现异常,直接抛出给下一个then的回调 let result = failCallback(this.reason) /* 判断回调函数返回值的类型 如果是普通值,直接调用resolve传递给下一个then方法 如果是一个新的Promise,调用返回值的then方法将状态床底给下一个then方法 处理循环调用其实就是判断result和promise2是否相等,由于promise2没有执行完成的时候没有办法将它作为参数传递,所以这里用了一个异步的方式 */ resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }, 0) }) } }) return promise2 } } // 重复操作单独定义一个方法 function resolvePromise(promise2, result, resolve, reject) { // 如果返回的Promise处理的结果就是promise2,直接抛出异常 if (promise2 === result) { return reject(new TypeError('循环引用啦!')) } if (result instanceof MyPromise) { result.then( (value) => resolve(value), (reason) => reject(reason) ) } else { resolve(result) } }
至此,promise的基本实现已经完成的差不多了,那除了then方法之外,原生的Promise对象还有catch,finally两个方法,还有Promise.all,Promise.race,Promise.resolve三个静态方法,这三个方法的实现就放在下一期吧。