[前端] 万字详解JavaScript手写一个Promise

2011 0
黑夜隐士 2022-10-21 15:53:11 | 显示全部楼层 |阅读模式
目录

    前言Promise核心原理实现
      Promise的使用分析MyPromise的实现
    在Promise中加入异步操作实现then方法的多次调用实现then的链式调用then方法链式调用识别Promise对象自返回捕获错误及 then 链式调用其他状态代码补充
      捕获执行器错误捕获then中的报错错误与异步状态的链式调用
    将then方法的参数变成可选参数Promise.all方法的实现Promise.resolve方法的实现finally方法的实现catch方法的实现完整代码


前言

手写Promise现在已经成了面试的热门内容,但在实际开发中基本都不会去手写一个Promise,但是在面试中各种手写题可能就会遇到一个手写Promise,我们可以尽量提高我们的上限,从而获取更多的工作机会。


Promise核心原理实现

首先我们从使用的角度来分析一下Promise,然后编写一个最简单版本的Promise。

Promise的使用分析

Promise就是一个类,在执行这个类的时候,需要传递一个执行器(回调函数)进去,执行器会立即执行。
Promise中的状态分为三个,分别是:
    pending→等待fulfilled→成功rejected→失败
状态的切换只有两种,分别是:
    pending→fulfilledpending→rejected
一旦状态发生改变,就不会再次改变:
    执行器中的两个参数,分别是resolve和reject,其实就是两个回调函数,调用resolve是从pending状态到fulfilled,调用reject是从状态pending到rejected。传递给这两个回调函数的参数会作为成功或失败的值。Promise的实例对象具有一个then方法,该方法接受两个回调函数,分别来处理成功与失败的状态,then方法内部会进行判断,然后根据当前状态确定调用的回调函数。then方法应该是被定义在原型对象中的。then的回调函数中都包含一个值,如果是成功,表示成功后返回的值;如果是失败就表示失败的原因。

MyPromise的实现

根据我们上面的分析,写出如下代码:
  1. MyPromise.js
  2. // 定义所有状态的常量
  3. const PENDING = 'pending'
  4. const FULFILLED = 'fulfilled'
  5. const REJECTED = 'rejected'
  6. // Promise实质上就是一个类,首先创建一个Promise的类
  7. class MyPromise {
  8.     // 实例化Promise时需要一个回调函数,该回调函数立即执行
  9.     constructor(executor) {
  10.         // 在调用executor需要传递两个回调函数,分别是resolve和reject
  11.         executor(this.resolve, this.reject)
  12.     }
  13.     // Promise 的状态
  14.     status = PENDING
  15.     // 记录成功与失败的值
  16.     value = undefined
  17.     reason = undefined
  18.     resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
  19.         // 形参value表示,调用resolve时传递的参数
  20.         // 如果当前状态不是pending,就直接跳出该逻辑
  21.         if (this.status !== PENDING) return
  22.         // 将状态修改为成功
  23.         this.status = FULFILLED
  24.         // 将传入的值进行保存
  25.         this.value = value
  26.     }
  27.     reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
  28.         // 形参reason表示,调用reject时传递的失败的原因
  29.         // 如果当前状态不是pending,就直接跳出该逻辑
  30.         if (this.status !== PENDING) return
  31.         // 将状态修改为失败
  32.         this.status = REJECTED
  33.         // 保存失败的原因
  34.         this.reason = reason
  35.     }
  36.     // then方法的实现
  37.     then (onFulfilled, onRejected) {
  38.         // 判断当前状态,根据状态调用指定回调
  39.         if (this.status === FULFILLED) {
  40.             // 将成功的值作为参数返回
  41.             onFulfilled(this.value)
  42.         } else if (this.status === REJECTED) {
  43.             // 将失败的原因作为参数返回
  44.             onRejected(this.reason)
  45.         }
  46.     }
  47. }
  48. // 导出Promise
  49. module.exports = MyPromise
复制代码
现在我们就来写一段代码验证一下上面的代码
验证resolve:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     resolve('成功')
  4. })
  5. promise.then(value => {
  6.     console.log(value);
  7. }, reason => {
  8.     console.log(reason);
  9. })
  10. /* 输出
  11.     成功
  12. */
复制代码
验证reject:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     reject('失败')
  4. })
  5. promise.then(value => {
  6.     console.log(value);
  7. }, reason => {
  8.     console.log(reason);
  9. })
  10. /* 输出
  11.     失败
  12. */
复制代码
验证状态不可变:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     resolve('成功')
  4.     reject('失败')
  5. })
  6. promise.then(value => {
  7.     console.log(value);
  8. }, reason => {
  9.     console.log(reason);
  10. })
  11. /* 输出
  12.     成功
  13. */
复制代码
在Promise中加入异步操作

如果我们的代码中存在异步操作,我们自己写的Promise将毫无用处,
例如下面这段代码:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     setTimeout(resolve, 2000, '成功')
  4. })
  5. promise.then(value => {
  6.     console.log(value);
  7. }, reason => {
  8.     console.log(reason);
  9. })
复制代码
这段代码2000ms后没有任何输出,为了解决这个问题我们将对自己编写的类进行如下操作:
    创建两个实例方法用于存储我们传入的成功与失败的处理逻辑。为then方法添加状态为pending时的处理逻辑,这时将传递进来的属性保存到实例上。在成功或者失败时调用相应的传递进来的回调函数(实例属性存在函数的情况下)。
实现代码如下:
  1. // MyPromise.js
  2. const PENDING = 'pending'
  3. const FULFILLED = 'fulfilled'
  4. const REJECTED = 'rejected'
  5. class MyPromise {
  6.     constructor(executor) {
  7.         executor(this.resolve, this.reject)
  8.     }
  9.     status = PENDING
  10.     value = undefined
  11.     reason = undefined
  12.     // 存储成功与失败的处理逻辑
  13.     onFulfilled = undefined
  14.     onRejected = undefined
  15.     resolve = (value) => {
  16.         if (this.status !== PENDING) return
  17.         this.status = FULFILLED
  18.         this.value = value
  19.         // 如果将状态修复为成功,调用成功的回调
  20.         this.onFulfilled && this.onFulfilled(this.value)
  21.     }
  22.     reject = (reason) => {
  23.         if (this.status !== PENDING) return
  24.         this.status = REJECTED
  25.         this.reason = reason
  26.         // 如果将状态修复为失败,调用失败的回调
  27.         this.onRejected && this.onRejected(this.reason)
  28.     }
  29.     then (onFulfilled, onRejected) {
  30.         if (this.status === FULFILLED) {
  31.             onFulfilled(this.value)
  32.         } else if (this.status === REJECTED) {
  33.             onRejected(this.reason)
  34.         } else {
  35.             // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
  36.             this.onFulfilled = onFulfilled
  37.             this.onRejected = onRejected
  38.         }
  39.     }
  40. }
  41. module.exports = MyPromise
复制代码
这里的this.onFulfilled && this.onFulfilled(this.value)表示如果该属性存在就调用这个方法。
现在我们重新执行一开始上面那一段代码,2s后会成功输出成功。

实现then方法的多次调用

Promise实例中存在要给then方法,允许我们在Promise实例中链式调用,每个then方法还会返回一个Promise实例,
如下图所示:


Promise实例方法then是可以多次进行调用,而我们现在自己封装的却执行调用一次,现在根据新需要来重新改写我们的代码,
实现思路如下:
    定义可以存储多个回调的数组,用于存储多个回调函数。如果是同步执行的代码,执行后立即知道执行结果,所以可以直接调用回调函数。如果是异步代码,需要将每次回调函数保存到数组中,然后状态变化时依次调用函数。
实现代码如下:
  1. // MyPromise.js
  2. const PENDING = 'pending'
  3. const FULFILLED = 'fulfilled'
  4. const REJECTED = 'rejected'
  5. class MyPromise {
  6.     constructor(executor) {
  7.         executor(this.resolve, this.reject)
  8.     }
  9.     status = PENDING
  10.     value = undefined
  11.     reason = undefined
  12.     // 存储成功与失败的处理逻辑
  13.     onFulfilled = []
  14.     onRejected = []
  15.     resolve = (value) => {
  16.         if (this.status !== PENDING) return
  17.         this.status = FULFILLED
  18.         this.value = value
  19.         // 如果将状态修复为成功,调用成功的回调
  20.         while (this.onFulfilled.length) {
  21.             // Array.prototype.shift() 用于删除数组第一个元素,并返回
  22.             this.onFulfilled.shift()(this.value)
  23.         }
  24.     }
  25.     reject = (reason) => {
  26.         if (this.status !== PENDING) return
  27.         this.status = REJECTED
  28.         this.reason = reason
  29.         // 如果将状态修复为失败,调用失败的回调
  30.         while (this.onRejected.length) {
  31.             // Array.prototype.shift() 用于删除数组第一个元素,并返回
  32.             this.onRejected.shift()(this.reason)
  33.         }
  34.     }
  35.     then (onFulfilled, onRejected) {
  36.         if (this.status === FULFILLED) {
  37.             onFulfilled(this.value)
  38.         } else if (this.status === REJECTED) {
  39.             onRejected(this.reason)
  40.         } else {
  41.             // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
  42.             this.onFulfilled.push(onFulfilled)
  43.             this.onRejected.push(onRejected)
  44.         }
  45.     }
  46. }
  47. module.exports = MyPromise
复制代码
这里我们通过数组的shift()方法,该方法删除数组的第一个元素,并返回,返回的这个值正好是一个回调函数,然后调用该函数即可实现该功能。

实现then的链式调用

想要实现then的链式调用,主要解决两个问题:
    返回的是一个新的MyPormise实例。then的返回值作为下一次的链式调用的参数。
这里分为两种情况:
    直接返回一个值,可以直接作为值使用返回一个新的MyPormise实例,此时就需要判断其状态
实现代码如下:
  1. // MyPromise.js
  2. /* 省略的代码同上 */
  3. class MyPromise {
  4.     /* 省略的代码同上 */
  5.     // then方法的实现
  6.     then (onFulfilled, onRejected) {
  7.         // then 方法返回一个MyPromise实例
  8.         return new MyPromise((resolve, reject) => {
  9.             // 判断当前状态,根据状态调用指定回调
  10.             if (this.status === FULFILLED) {
  11.                 // 将成功的值作为参数返回
  12.                 // 保存执行回调函数的结果
  13.                 const result = onFulfilled(this.value)
  14.                 // 如果返回的是一个普通的值,直接调用resolve
  15.                 // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
  16.                 resolvePromise(result, resolve, reject)
  17.             } else if (this.status === REJECTED) {
  18.                 // 将失败的原因作为参数返回
  19.                 onRejected(this.reason)
  20.             } else {
  21.                 // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
  22.                 this.onFulfilled.push(onFulfilled)
  23.                 this.onRejected.push(onRejected)
  24.             }
  25.         })
  26.     }
  27. }
  28. function resolvePromise (result, resolve, reject) {
  29.     // 判断传递的result是不是MyPromise的实例对象
  30.     if (result instanceof MyPromise) {
  31.         // 说明是MyPromise的实例对象
  32.         // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调
  33.         // result.then(value => resolve(value), reason => reject(reason))
  34.         // 简写
  35.         result.then(resolve, reject)
  36.     } else {
  37.         resolve(result)
  38.     }
  39. }
  40. module.exports = MyPromise
复制代码
then方法链式调用识别Promise对象自返回

在Promise中,如果then方法返回的是自己的promise对象,则会发生promise的嵌套,这个时候程序会报错,
测试代码如下:
  1. var promise = new Promise((resolve, reject) => {
  2.   resolve(100)
  3. })
  4. var p1 = promise.then(value => {
  5.   console.log(value)
  6.   return p1
  7. })
  8. // 100
  9. // Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
复制代码
想要解决这个问题其实我们只需要在then方法返回的MyPromise实例对象与then中回调函数返回的值进行比对,如果相同的返回一个reject的MyPromise实例对象,并创建一个TypeError类型的Error。
实现代码如下:
  1. // MyPromise.js
  2.     /* 省略的代码同上 */
  3.     then (onFulfilled, onRejected) {
  4.         // then 方法返回一个MyPromise实例
  5.         const promise = new MyPromise((resolve, reject) => {
  6.             // 判断当前状态,根据状态调用指定回调
  7.             if (this.status === FULFILLED) {
  8.                 // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
  9.                 // 如果不变成异步的话是在函数内获取不到promise的
  10.                 setTimeout(() => {
  11.                     // 将成功的值作为参数返回
  12.                     // 保存执行回调函数的结果
  13.                     const result = onFulfilled(this.value)
  14.                     // 如果返回的是一个普通的值,直接调用resolve
  15.                     // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
  16.                     resolvePromise(promise, result, resolve, reject)
  17.                 }, 0)
  18.             } else if (this.status === REJECTED) {
  19.                 // 将失败的原因作为参数返回
  20.                 onRejected(this.reason)
  21.             } else {
  22.                 // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
  23.                 this.onFulfilled.push(onFulfilled)
  24.                 this.onRejected.push(onRejected)
  25.             }
  26.         })
  27.         return promise
  28.     }
  29. }
  30. function resolvePromise (promise, result, resolve, reject) {
  31.     // 这里修改一下该函数,如果return的Promise实例对象,也就是传入的promise===result的话,说明在promise中return的是当前promise对象。
  32.     if (promise === result) {
  33.         // 这里调用reject,并抛出一个Error
  34.         // return 是必须的,阻止程序向下执行
  35.         return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  36.     }
  37.     // 判断传递的result是不是MyPromise的实例对象
  38.     if (result instanceof MyPromise) {
  39.         // 说明是MyPromise的实例对象
  40.         // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调
  41.         // result.then(value => resolve(value), reason => reject(reason))
  42.         // 简写
  43.         result.then(resolve, reject)
  44.     } else {
  45.         resolve(result)
  46.     }
  47. }
  48. module.exports = MyPromise
复制代码
这里then方法中的setTimeout的作用并不是延迟执行,而是为了调用resolvePromise函数时,保证创建的promise存在。

捕获错误及 then 链式调用其他状态代码补充

到目前为止我们现实的Promise并没有对异常做任何处理,为了保证代码的健壮性,我们需要对异常做一些处理。

捕获执行器错误

现在我们需要对执行器进行异常捕获,如果发生异常,就将我们的状态修改为rejected。
捕获执行器的错误也比较简单,只需要在构造函数中加入try...catch语句就可以,
实现代码如下:
  1.     constructor(executor) {
  2.         try {
  3.             // 在调用executor需要传递两个回调函数,分别是resolve和reject
  4.             executor(this.resolve, this.reject)
  5.         } catch (e) {
  6.             // 发生异常调用reject
  7.             this.reject(e)
  8.         }
  9.     }
复制代码
测试代码如下:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     throw new Error('执行器错误')
  4. })
  5. promise.then(value => {
  6.     console.log(value);
  7. }, error => {
  8.     console.log(error.message);
  9. })
  10. /* 输出
  11.     执行器错误
  12. */
复制代码
捕获then中的报错

现在我们需要对then中的异常捕获到,并在下一次链式调用中传递到then的第二个函数中,实现的方式也是通过try...catch语句,
示例代码如下:
  1. // then方法的实现
  2. then (onFulfilled, onRejected) {
  3.     // then 方法返回一个MyPromise实例
  4.     const promise = new MyPromise((resolve, reject) => {
  5.         // 判断当前状态,根据状态调用指定回调
  6.         if (this.status === FULFILLED) {
  7.             // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
  8.             // 如果不变成异步的话是在函数内获取不到promise的
  9.             setTimeout(() => {
  10.                 try {
  11.                     // 将成功的值作为参数返回
  12.                     // 保存执行回调函数的结果
  13.                     const result = onFulfilled(this.value)
  14.                     // 如果返回的是一个普通的值,直接调用resolve
  15.                     // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
  16.                     resolvePromise(promise, result, resolve, reject)
  17.                 } catch (error) {
  18.                     reject(error)
  19.                 }
  20.             }, 0)
  21.         } else if (this.status === REJECTED) {
  22.             // 将失败的原因作为参数返回
  23.             onRejected(this.reason)
  24.         } else {
  25.             // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
  26.             this.onFulfilled.push(onFulfilled)
  27.             this.onRejected.push(onRejected)
  28.         }
  29.     })
  30.     return promise
  31. }
复制代码
测试代码如下:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     resolve('成功')
  4. })
  5. // 第一个then方法中的错误要在第二个then方法中捕获到
  6. promise.then(value => {
  7.     console.log('resolve', value)
  8.     throw new Error('then的执行过程中遇到异常')
  9. }).then(null, reason => {
  10.     console.log(reason.message)
  11. })
  12. /* 输出
  13.     resolve 成功
  14.     then的执行过程中遇到异常
  15. */
复制代码
错误与异步状态的链式调用

现在只对成功状态的then进行的链式调用以及错误处理,错误与异步状态未进行处理,其实处理起来也是一样的,
示例代码如下:
  1.     // then方法的实现
  2.     then (onFulfilled, onRejected) {
  3.         // then 方法返回一个MyPromise实例
  4.         const promise = new MyPromise((resolve, reject) => {
  5.             // 判断当前状态,根据状态调用指定回调
  6.             if (this.status === FULFILLED) {
  7.                 // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
  8.                 // 如果不变成异步的话是在函数内获取不到promise的
  9.                 setTimeout(() => {
  10.                     try {
  11.                         // 将成功的值作为参数返回
  12.                         // 保存执行回调函数的结果
  13.                         const result = onFulfilled(this.value)
  14.                         // 如果返回的是一个普通的值,直接调用resolve
  15.                         // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
  16.                         resolvePromise(promise, result, resolve, reject)
  17.                     } catch (error) {
  18.                         reject(error)
  19.                     }
  20.                 }, 0)
  21.             } else if (this.status === REJECTED) {
  22.                 // 失败的处理同成功处理,只是调用的回调函数不同
  23.                 setTimeout(() => {
  24.                     try {
  25.                         const result = onRejected(this.reason)
  26.                         resolvePromise(promise, result, resolve, reject)
  27.                     } catch (error) {
  28.                         reject(error)
  29.                     }
  30.                 }, 0)
  31.             } else {
  32.                 this.onFulfilled.push((value) => {
  33.                     setTimeout(() => {
  34.                         try {
  35.                             const result = onFulfilled(value)
  36.                             resolvePromise(promise, result, resolve, reject)
  37.                         } catch (error) {
  38.                             reject(error)
  39.                         }
  40.                     }, 0)
  41.                 })
  42.                 this.onRejected.push((reason) => {
  43.                     setTimeout(() => {
  44.                         try {
  45.                             const result = onRejected(reason)
  46.                             resolvePromise(promise, result, resolve, reject)
  47.                         } catch (error) {
  48.                             reject(error)
  49.                         }
  50.                     }, 0)
  51.                 })
  52.             }
  53.         })
  54.         return promise
  55.     }
复制代码
测试代码如下:
  1. const MyPromise = require('./myPromise')
  2. let promise = new MyPromise((resolve, reject) => {
  3.     setTimeout(resolve, 2000, '成功')
  4. })
  5. // 第一个then方法中的错误要在第二个then方法中捕获到
  6. promise.then(value => {
  7.     console.log('resolve', value)
  8.     throw new Error('then的执行过程中遇到异常')
  9. }).then(null, reason => {
  10.     console.log(reason.message)
  11. })
  12. /* 输出
  13.     resolve 成功
  14.     then的执行过程中遇到异常
  15. */
复制代码
将then方法的参数变成可选参数

Promise中的then方法其实是两个可以可选参数,如果我们不传递任何参数的话,里面的结果是向下传递的,直到捕获为止,
例如下面这段代码:
  1. new Promise((resolve, reject) => {
  2.     resolve(100)
  3. })
  4.     .then()
  5.     .then()
  6.     .then()
  7.     .then(value => console.log(value))
  8. // 最后一个then输入100
复制代码
这段代码可以理解为:
  1. new Promise((resolve, reject) => {
  2.     resolve(100)
  3. })
  4.     .then(value => value)
  5.     .then(value => value)
  6.     .then(value => value)
  7.     .then(value => console.log(value))
复制代码
所以说我们只需要在没有传递回调函数时,赋值一个默认的回调函数即可。
实现代码如下:
  1. // then方法的实现
  2. then (onFulfilled, onRejected) {
  3.     // 如果传递函数,就是用传递的函数,否则指定一个默认值,用于参数传递
  4.     onFulfilled = onFulfilled ? onFulfilled : value => value
  5.     // 同理
  6.     onRejected = onRejected ? onRejected : reason => { throw reason }
  7.     // then 方法返回一个MyPromise实例
  8.     const promise = new MyPromise((resolve, reject) => {
  9.         // 判断当前状态,根据状态调用指定回调
  10.         if (this.status === FULFILLED) {...
  11.         } else if (this.status === REJECTED) {...
  12.         } else {...
  13.         }
  14.     })
  15.     return promise
  16. }
复制代码
Promise.all方法的实现

关于all()方法的使用,可以参数Promise.all()。简单的说Promise.all()会将多个Promise实例包装为一个Promise实例,且顺序与调用顺序一致,
示例代码如下:
  1. function p1 () {
  2.     return new Promise((resolve, reject) => {
  3.         setTimeout(() => {
  4.             resolve('p1')
  5.         }, 2000)
  6.     })
  7. }
  8. function p2 () {
  9.     return new Promise((resolve, reject) => {
  10.         setTimeout(() => {
  11.             resolve('p2')
  12.         }, 0)
  13.     })
  14. }
  15. Promise.all(['a', 'b', p1(), p2(), 'c']).then(result => {
  16.     console.log(result)
  17.     // ["a", "b", "p1", "p2", "c"]
  18. })
复制代码
在这段代码中,我们的p1的执行是延迟了2s的,这里如果不使用Promise.all()的话最终顺序是与我们调用不同的。
现在我们来分析一下all()的实现思路:
    all()方法可以通过类直接调用,所以是一个静态方法all()方法接收一个数组,数组中的值可以是一个普通值,也可以是一个MyPromise的实例对象return一个新的MyPromise实例对象遍历数组中的每一个值,判断值得类型,如果是一个普通值得话直接将值存入一个数组;如果是一个MyPromise的实例对象的话,会调用then方法,然后根据执行后的状态,如果失败的话调用新的MyPromise实例对象中的rejecte,如果是成功话将这个值存入一个数组存入数组时计数,如果存入的数量达到传入的数组长度,说明调用完毕,执行resolve并将最终的结果数组作为参数返回。
实现代码:
  1. /**
  2. * @description: 将多个Promise实例合并为一个Promise实例
  3. * @param {*} array Promise或者普通值
  4. * @returns {Promise}
  5. */
  6. static all (array) {
  7.     // 用于存放最终结果的数组
  8.     let result = []
  9.     // 用于计算当前已经执行完的实例的数量
  10.     let count = 0
  11.     // 最后返回的是一个Promise实例
  12.     return new MyPromise((resolve, reject) => {
  13.         /**
  14.          * @description: 将执行完毕的值加入结果数组,并根据实际情况决定是否调用resolve
  15.          * @param {*} result 存放结果的数组
  16.          * @param {*} index 要加入的索引
  17.          * @param {*} value 要加入数组的值
  18.          * @param {*} resolve Promise中的resolve
  19.          */
  20.         function addResult (result, index, value, resolve) {
  21.             // 根据索引值,将结果堆入数组中
  22.             result[index] = value
  23.             // 执行完毕一个 count+1,如果当前值等于总长度的话说明已经执行结束了,可以直接调用resolve,说明已经成功执行完毕了
  24.             if (++count === array.length) {
  25.                 // 将执行结果返回
  26.                 resolve(result)
  27.             }
  28.         }
  29.         // 遍历穿入的数组,每个都执行then方法,获取到最终的结果
  30.         array.forEach((p, index) => {
  31.             // 判断p是不是MyPromise的实例,如果是的话调用then方法,不是直接将值加入数组中
  32.             if (p instanceof MyPromise) {
  33.                 p.then(
  34.                     // 成功时将结果直接加入数组中
  35.                     value => {
  36.                         addResult(result, index, value, resolve)
  37.                     },
  38.                     // 如果失败直接返回失败原因
  39.                     reason => {
  40.                         reject(reason)
  41.                     }
  42.                 )
  43.             }
  44.             else {
  45.                 addResult(result, index, p, resolve)
  46.             }
  47.         })
  48.     })
  49. }
复制代码
Promise.resolve方法的实现

关于Promise.resolve()方法的用法可以参考Promise.resolve()与Promise.reject()。
我们实现的思路主要如下:
    该方法是一个静态方法该方法接受的如果是一个值就将该值包装为一个MyPromise的实例对象返回,如果是一个MyPromise的实例对象,调用then方法返回。
实现代码如下:
  1. static resolve (value) {
  2.     // 如果是MyPromise的实例,就直接返回这个实例
  3.     if (value instanceof MyPromise) return value
  4.     // 如果不是的话创建一个MyPromise实例,并返回传递的值
  5.     return new MyPromise((resolve) => {
  6.         resolve(value)
  7.     })
  8. }
复制代码
finally方法的实现

关于finally方法可参考finally(),实现该方法的实现代码如下:
  1. finally (callback) {
  2.     // 如何拿到当前的promise的状态,使用then方法,而且不管怎样都返回callback
  3.     // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可
  4.     // 我们需要在回调之后拿到成功的回调,所以需要把value也return
  5.     // 失败的回调也抛出原因
  6.     // 如果callback是一个异步的promise对象,我们还需要等待其执行完毕,所以需要用到静态方法resolve
  7.     return this.then(value => {
  8.         // 把callback调用之后返回的promise传递过去,并且执行promise,且在成功之后返回value
  9.         return MyPromise.resolve(callback()).then(() => value)
  10.     }, reason => {
  11.         // 失败之后调用的then方法,然后把失败的原因返回出去。
  12.         return MyPromise.resolve(callback()).then(() => { throw reason })
  13.     })
  14. }
复制代码
测试:
  1. function p1 () {
  2.     return new MyPromise((resolve, reject) => {
  3.         setTimeout(() => {
  4.             resolve('p1')
  5.         }, 2000)
  6.     })
  7. }
  8. function p2 () {
  9.     return new MyPromise((resolve, reject) => {
  10.         reject('p2 reject')
  11.     })
  12. }
  13. p2().finally(
  14.     () => {
  15.         console.log('finally p2')
  16.         return p1()
  17.     }
  18. ).then(
  19.     value => {
  20.         console.log(value)
  21.     }, reason => {
  22.         console.log(reason)
  23.     }
  24. )
  25. // finally p2
  26. // 两秒之后执行p2 reject
复制代码
catch方法的实现

关于catch方法可以参考catch(),实现该方法其实非常简单,只需要在内部调用then方法,不传递第一个回调函数即可,
实现代码如下:
  1. catch (callback) {
  2.     return this.then(null, failCallback)
  3. }
复制代码
测试如下:
  1. function p () {
  2.     return new MyPromise((resolve, reject) => {
  3.         reject(new Error('reject'))
  4.     })
  5. }
  6. p()
  7.     .then(value => {
  8.         console.log(value)
  9.     })
  10.     .catch(reason => console.log(reason))
复制代码
完整代码
  1. // MyPromise.js
  2. // 定义所有状态的常量
  3. const PENDING = 'pending'
  4. const FULFILLED = 'fulfilled'
  5. const REJECTED = 'rejected'
  6. // Promise实质上就是一个类,首先创建一个Promise的类
  7. class MyPromise {
  8.     // 实例化Promise时需要一个回调函数,该回调函数立即执行
  9.     constructor(executor) {
  10.         try {
  11.             // 在调用executor需要传递两个回调函数,分别是resolve和reject
  12.             executor(this.resolve, this.reject)
  13.         } catch (e) {
  14.             // 发生异常调用reject
  15.             this.reject(e)
  16.         }
  17.     }
  18.     // Promise 的状态
  19.     status = PENDING
  20.     // 记录成功与失败的值
  21.     value = undefined
  22.     reason = undefined
  23.     // 存储成功与失败的处理逻辑
  24.     onFulfilled = []
  25.     onRejected = []
  26.     resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
  27.         // 形参value表示,调用resolve时传递的参数
  28.         // 如果当前状态不是pending,就直接跳出该逻辑
  29.         if (this.status !== PENDING) return
  30.         // 将状态修改为成功
  31.         this.status = FULFILLED
  32.         // 将传入的值进行保存
  33.         this.value = value
  34.         // 如果将状态修复为成功,调用成功的回调
  35.         // this.onFulfilled && this.onFulfilled(this.value)
  36.         while (this.onFulfilled.length) {
  37.             // Array.prototype.shift() 用于删除数组第一个元素,并返回
  38.             this.onFulfilled.shift()(this.value)
  39.         }
  40.     }
  41.     reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
  42.         // 形参reason表示,调用reject时传递的失败的原因
  43.         // 如果当前状态不是pending,就直接跳出该逻辑
  44.         if (this.status !== PENDING) return
  45.         // 将状态修改为失败
  46.         this.status = REJECTED
  47.         // 保存失败的原因
  48.         this.reason = reason
  49.         // 如果将状态修复为失败,调用失败的回调
  50.         // this.onRejected && this.onRejected(this.reason)
  51.         while (this.onRejected.length) {
  52.             // Array.prototype.shift() 用于删除数组第一个元素,并返回
  53.             this.onRejected.shift()(this.reason)
  54.         }
  55.     }
  56.     // then方法的实现
  57.     then (onFulfilled, onRejected) {
  58.         // 如果传递函数,就是用传递的函数,否则指定一个默认值,用于参数传递
  59.         onFulfilled = onFulfilled ? onFulfilled : value => value
  60.         // 同理
  61.         onRejected = onRejected ? onRejected : reason => { throw reason }
  62.         // then 方法返回一个MyPromise实例
  63.         const promise = new MyPromise((resolve, reject) => {
  64.             // 判断当前状态,根据状态调用指定回调
  65.             if (this.status === FULFILLED) {
  66.                 // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
  67.                 // 如果不变成异步的话是在函数内获取不到promise的
  68.                 setTimeout(() => {
  69.                     try {
  70.                         // 将成功的值作为参数返回
  71.                         // 保存执行回调函数的结果
  72.                         const result = onFulfilled(this.value)
  73.                         // 如果返回的是一个普通的值,直接调用resolve
  74.                         // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
  75.                         resolvePromise(promise, result, resolve, reject)
  76.                     } catch (error) {
  77.                         reject(error)
  78.                     }
  79.                 }, 0)
  80.             } else if (this.status === REJECTED) {
  81.                 // 失败的处理同成功处理,只是调用的回调函数不同
  82.                 setTimeout(() => {
  83.                     try {
  84.                         const result = onRejected(this.reason)
  85.                         resolvePromise(promise, result, resolve, reject)
  86.                     } catch (error) {
  87.                         reject(error)
  88.                     }
  89.                 }, 0)
  90.             } else {
  91.                 // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
  92.                 // this.onFulfilled.push(onFulfilled)
  93.                 // this.onRejected.push(onRejected)
  94.                 this.onFulfilled.push((value) => {
  95.                     setTimeout(() => {
  96.                         try {
  97.                             const result = onFulfilled(value)
  98.                             resolvePromise(promise, result, resolve, reject)
  99.                         } catch (error) {
  100.                             reject(error)
  101.                         }
  102.                     }, 0)
  103.                 })
  104.                 this.onRejected.push((reason) => {
  105.                     setTimeout(() => {
  106.                         try {
  107.                             const result = onRejected(reason)
  108.                             resolvePromise(promise, result, resolve, reject)
  109.                         } catch (error) {
  110.                             reject(error)
  111.                         }
  112.                     }, 0)
  113.                 })
  114.             }
  115.         })
  116.         return promise
  117.     }
  118.     catch (callback) {
  119.         return this.then(null, callback)
  120.     }
  121.     finally (callback) {
  122.         // 如何拿到当前的promise的状态,使用then方法,而且不管怎样都返回callback
  123.         // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可
  124.         // 我们需要在回调之后拿到成功的回调,所以需要把value也return
  125.         // 失败的回调也抛出原因
  126.         // 如果callback是一个异步的promise对象,我们还需要等待其执行完毕,所以需要用到静态方法resolve
  127.         return this.then(value => {
  128.             // 把callback调用之后返回的promise传递过去,并且执行promise,且在成功之后返回value
  129.             return MyPromise.resolve(callback()).then(() => value)
  130.         }, reason => {
  131.             // 失败之后调用的then方法,然后把失败的原因返回出去。
  132.             return MyPromise.resolve(callback()).then(() => { throw reason })
  133.         })
  134.     }
  135.     /**
  136.      * @description: 将多个Promise实例合并为一个Promise实例
  137.      * @param {*} array Promise或者普通值
  138.      * @returns {Promise}
  139.      */
  140.     static all (array) {
  141.         // 用于存放最终结果的数组
  142.         let result = []
  143.         // 用于计算当前已经执行完的实例的数量
  144.         let count = 0
  145.         // 最后返回的是一个Promise实例
  146.         return new MyPromise((resolve, reject) => {
  147.             /**
  148.              * @description: 将执行完毕的值加入结果数组,并根据实际情况决定是否调用resolve
  149.              * @param {*} result 存放结果的数组
  150.              * @param {*} index 要加入的索引
  151.              * @param {*} value 要加入数组的值
  152.              * @param {*} resolve Promise中的resolve
  153.              */
  154.             function addResult (result, index, value, resolve) {
  155.                 // 根据索引值,将结果堆入数组中
  156.                 result[index] = value
  157.                 // 执行完毕一个 count+1,如果当前值等于总长度的话说明已经执行结束了,可以直接调用resolve,说明已经成功执行完毕了
  158.                 if (++count === array.length) {
  159.                     // 将执行结果返回
  160.                     resolve(result)
  161.                 }
  162.             }
  163.             // 遍历穿入的数组,每个都执行then方法,获取到最终的结果
  164.             array.forEach((p, index) => {
  165.                 // 判断p是不是MyPromise的实例,如果是的话调用then方法,不是直接将值加入数组中
  166.                 if (p instanceof MyPromise) {
  167.                     p.then(
  168.                         // 成功时将结果直接加入数组中
  169.                         value => {
  170.                             addResult(result, index, value, resolve)
  171.                         },
  172.                         // 如果失败直接返回失败原因
  173.                         reason => {
  174.                             reject(reason)
  175.                         }
  176.                     )
  177.                 }
  178.                 else {
  179.                     addResult(result, index, p, resolve)
  180.                 }
  181.             })
  182.         })
  183.     }
  184.     static resolve (value) {
  185.         // 如果是MyPromise的实例,就直接返回这个实例
  186.         if (value instanceof MyPromise) return value
  187.         // 如果不是的话创建一个MyPromise实例,并返回传递的值
  188.         return new MyPromise((resolve) => {
  189.             resolve(value)
  190.         })
  191.     }
  192. }
  193. function resolvePromise (promise, result, resolve, reject) {
  194.     // 这里修改一下该函数,如果return的Promise实例对象,也就是传入的promise===result的话,说明在promise中return的是当前promise对象。
  195.     if (promise === result) {
  196.         // 这里调用reject,并抛出一个Error
  197.         // return 是必须的,阻止程序向下执行
  198.         return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  199.     }
  200.     // 判断传递的result是不是MyPromise的实例对象
  201.     if (result instanceof MyPromise) {
  202.         // 说明是MyPromise的实例对象
  203.         // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调
  204.         // result.then(value => resolve(value), reason => reject(reason))
  205.         // 简写
  206.         result.then(resolve, reject)
  207.     } else {
  208.         resolve(result)
  209.     }
  210. }
  211. module.exports = MyPromise
复制代码
到此这篇关于万字详解JavaScript手写一个Promise的文章就介绍到这了,更多相关JS手写Promise内容请搜索中国红客联盟以前的文章或继续浏览下面的相关文章希望大家以后多多支持中国红客联盟!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行