彻底理解Promise原理及全功能实现

12,068 阅读10分钟

开篇

Promise作为前端异步解决方案的出现,可以说是火遍全网,几乎所有的异步场景甚至框架都会有它的身影,比如Vue的批量处理等。今天我们就按照Promise A+ 规范来完整实现Promise全功能,话不多说,上代码。

Promise 实现

status 状态定义

Promise的设定是一个不可逆的状态机,包含:

const PENDDING = "PENDDING"; // 初始化pendding状态
const RESOLVED = "RESOLVED"; // 正确完成resolve状态
const REJECTED = "REJECTED"; // 错误完成reject状态

MyPromise

创建MyPromise类函数和初始化相对应的值和状态

class MyPromise {
  constructor(executor) {
    // 初始化状态status
    // 返回值value
    // 错误原因reason
    this.status = PENDDING;
    this.value = undefined;
    this.reason = undefined;

    // 返回值回调队列和错误回调队列
    this.resolves = [];
    this.rejects = [];

    // 声明resolve函数
    const resolve = (value) => {
      if (this.status === PENDDING) {
        this.status = RESOLVED; // 变更状态为完成状态
        this.value = value; // 赋值

        // 执行resolves队列
        while (this.resolves.length) {
          const callback = this.resolves.shift();
          callback(value);
        }
      }
    };

    // 声明reject函数
    const reject = (reason) => {
      if (this.statue === PENDDING) {
        this.status = REJECTED; // 变更状态为拒绝状态
        this.reason = reason; // 赋值

        // 执行rejects队列
        while (this.rejects.length) {
          const callback = this.rejects.shift();
          callback(reason);
        }
      }
    };
    
    try{
    	executor(resolve,reject)
    }catch(e){
    	reject(e)
    }
  }
}

MyPromise.then

同步和异步

class MyPromise {
  // ...
  then(resolve, reject) {
    // 完成状态,推入完成队列
    if (this.status === RESOLVED) {
      resolve(this.value);
    }

    // 拒绝状态,推入拒绝队列
    if (this.status === REJECTED) {
      reject(this.reason);
    }

    // 异步情况
    if (this.status === PENDDING) {
      this.resolves.push(resolve);
      this.rejects.push(reject);
    }
  }
  // ...
}

// 测试同步任务

const promise = new MyPromise((resolve, reject) => {
    resolve('promise sync')
})

promise.then(res => {
    console.log(res)
})

// 打印结果
// promise sync

// 测试异步任务
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise async)
    }, 500)
})

promise.then(res => {
    console.log(res)
})

// 打印结果
// promise async

Promise A+规范场景

根据 Promise A+ 规范,then 方法会返回一个 promise,从而支持向下链式调用。同时可以根据上一个 then 的返回值,透传给下一个 then 方法。

// Promise A+
const promise = new Promise((resolve, reject) => {
  resolve("first");
});

// 第一种场景:返回常规值
promise
  .then((res) => {
    console.log(res);

    return "second";
  })
  .then((res) => {
    console.log(res);
  });

// 打印结果
// first
// second

// 第二种场景:返回promise
promise
  .then((res) => {
    console.log(res);
    return new Promise((resolve) => {
      resolve("promise");
    });
  })
  .then((res) => {
    console.log(res);
  });

// 打印结果
// first
// promise

// 第三种场景:值穿透
promise
  .then((res) => {
    console.log(res);
    return res;
  })
  .then()
  .then((res) => {
    console.log(res);
  });

// 打印结果
// first
// first

实现 then

根据以上规范定义,我们来改造一下then方法:

class MyPromise {
  // ...
  then(resolve, reject) {
    // 判断resolve和reject未传入的情况,解决空值透传问题
    // then()情况
    typeof resolve !== 'function' ? resolve = value => value : resolve
    typeof reject !== 'function' ? reject = reason => throw new Error(reason instanceof Error ? reason.message : reason )

    //根据规范,then会返回一个全新的promise
    return new MyPromise((resolveFn, rejectFn) => {
        // 重写传入的resolve方法
        // 判断返回值
        const fulfilished = value => {
            try{
                // 接收返回值
                const res = resolve(value)

                // 判断返回值类型:promise或普通类型
                // 如果是promise,则往下执行一次then
                // 如果是普通类型,则直接执行resolveFn,保证value是最新值
                res instanceof MyPromise ? res.then(resolveFn,rejectFn) : resolveFn(res)
            }catch(e) {
                rejectFn(e)
            }
        }

        // 重写传入的reject方法
        // 判断返回值
        const rejected = reason => {
            try{
        // 接收返回值
                const res = reject(reason)

                // 判断返回值类型:promise或普通类型
                // 如果是promise,则往下执行一次then
                // 如果是普通类型,则直接执行rejectFn,保证value是最新值
                res instanceof MyPromise ? res.then(resolveFn,rejectFn) : rejectFn(res)
            }catch(e){
                rejectFn(e instanceof Error ? e.message: e)
            }

        }

        // 判断同步异步任务
        // 执行相对应的方法
        // 这里用switch方法改进
        switch(this.status) {
            case RESOLVED:
                fulfilished(this.value)
            break;

            case REJECTED:
                rejected(this.reason)
            break;

            case PENDDING:
                this.resolves.push(fulfilished)
                this.rejects.push(rejected)
              break;
        }
    })

  }
  // ...
}

// 测试
const promise = new MyPromise((resolve, reject) => {
    resolve('first')
})

promise.then(res => {
    console.log(res)
    return new MyPromise((resolve, reject) => {
        resolve('promise second')
    })
}).then().then(res => {
    console.log(res)
    return 'third'
}).then(res => {
    console.log(res)
})

// 打印结果
// first
// promise second
// third

小结

测试成功,promise的改造就算符合规范了。这个难点在于then内函数的返回值如果是promise,那么我们会先让他执行注册一次then,让promise接着往下执行。

MyPromise.catch

catch方法相对比较简单,将拒绝的值放到reject方法里执行就可以。

Promise A+规范场景

// Promise A+
const promise = new Promise((resolve, reject) => {
  reject("promise reject");
});

promise.catch((e) => {
  console.log(e);
});

// 打印结果
// promise reject

实现 catch

class MyPromise {
  // ...
  catch(errorFn) {
    // 这里只需注册执行下then,传入callback就能实现
    this.then(null, errorFn);
  }
  // ...
}

// 测试
const promise = new MyPromise((resolve, reject) => {
  reject("my promise reject");
});

promise.catch((e) => {
  console.log(e);
});

// 打印结果
// my promise reject

小结

catch方法在于执行回调去获取reject的结果,所以只需执行一下then并传入callback就实现了,相对好理解。

MyPromise.all

业务场景中,我们经常会遇到不止一个promie的场景,因此需要合并一次执行多个promise,统一返回结果,Promise.all就是为了解决此问题。

Promise A+规范场景

  // Promise A+
  // 创建三个promise
  const promise1 = Promise.resolve(1)
  const promise2 = Promise.resolve(2)
  const promise3 = Promise.resolve(3)

  Promise.all([promise1,promise12,promise3]).then(res => {
    console.log(res)
  })

  // 打印结果
  // [1,2,3]

  // 添加一个reject
  const promise4 = Promise.resolve(1)
  const promise5 = Promise.reject('reject')
  const promise6 = Promise.resolve(3)

  Promise.all([promise4, promise5,promise6]).then(res => {
    console.log(res, 'resolve')
  }).catch(e => {
    console.log(e)
  })

  // 打印结果
  // reject

根据Promise A+规范,Promise.all可以同时执行多个Promise,并且在所有的Promise方法都返回完成之后才返回一个数组返回值。当有其中一个Promise reject的时候,则返回reject的结果。

实现 Promise.all

我们来实现一下:

  class MyPromise {
    // ...
    // all是静态方法
    static all(promises) {
      // 已然是返回一个promise
      return new MyPromise((resolve, reject) => {
        // 创建一个收集返回值的数组
        const result = []

        // 执行
        deepPromise(promises[0], 0 , result)

        // 返回结果
        resolve(result)

        // 这里我们用递归来实现
        // @param {MyPromise} promise 每一个promise方法
        // @param {number} index 索引
        // @param {string[]} result 收集返回结果的数组
        function deepPromise(promise, index, result) {
          // 边界判断
          // 所有执行完之后返回收集数组
          if(index > promises.length - 1) {
            return result
          }

          if(typeof promise.then === 'function') {
            // 如果是promise
            promise.then(res => {
              index++
              result.push(res)
              deepPromise(promises[index], index, result)
            }).catch(e => {
              // reject直接返回
              reject(e instanceof Error ? e.message : e)
            })
          }else {
            // 如果是普通值
            // 这里我们只做简单判断,非promise则直接当返回值处理
            index++
            result.push(promise)
            deepPromise(promises[index], index, res)
          }
        }
      })

    }
    // ...
  }

  // 测试
  // 创建三个MyPromise
  const promise1 = MyPromise.resolve(1)
  const promise2 = MyPromise.resolve(2)
  const promise3 = MyPromise.resolve(3)

  MyPromise.all([promise1,promise12,promise3]).then(res => {
    console.log(res)
  })

  // 打印结果
  // [1,2,3]

  // 添加一个reject
  const promise4 = MyPromise.resolve(1)
  const promise5 = MyPromise.reject('reject')
  const promise6 = MyPromise.resolve(3)

  MyPromise.all([promise4, promise5,promise6]).then(res => {
    console.log(res, 'resolve')
  }).catch(e => {
    console.log(e)
  })

  // 打印结果
  // reject

小结

Promise.all作为一个批量处理的函数,让我们在使用时可以同时多个处理promise,简化了逐个执行的劣势。核心逻辑也相对比较简单,最重要的点在于执行完一个promise后再去执行下一个promise,处理完这个逻辑也就基本完成了Promise.all的全功能了。

MyPromise.resolve

静态方法resolve的实现就相对简单了,返回一个promise,传入对应参数即可。

实现MyPromise.resolve

  class MyPromise {
    // ...
    static resolve(value) {
      return new MyPromise((resolveFn, rejectFn) => {
        resolveFn(value)
      })
    }
    // ...
  }

  // 测试
  MyPromise.resolve('static resolve').then(res => {
    console.log(res)
  })

  // 打印结果
  // static resolve

MyPromise.reject

静态方法reject的实现跟resolve是类似,返回一个promise,传入对应参数即可。

  class MyPromise {
    // ...
    static reject(reason) {
      return new MyPromise((resolveFn, rejectFn) => {
        rejectFn(reason)
      })
    }
    // ...
  }

  // 测试
  MyPromise.reject('static reject').catch(e => {
    console.log(res)
  })

  // 打印结果
  // static reject

什么是 allSettled?

ECMA官网最新更新了Promise的新的静态方法Promise.allSettled,那么这是一个怎样的方法呢?总体来讲他也是一个批量处理Promise的函数,但是我们已经有了Promise.all,为什么还需要allSettled。要解开这个问题,我们得回顾一下Promise.all。现有的Promise.all我们说过,如果Promise队列里有一个reject,那么他就只会返回reject,所以Promise.all不一定会返回所有结果,很显然Promise.allSettled能够解决这个问题。

Promise A+测试场景

   // Promise A+
  // 创建三个promise
  const promise1 = Promise.resolve(1)
  const promise2 = Promise.resolve(2)
  const promise3 = Promise.resolve(3)

  Promise.allSettled([promise1,promise12,promise3]).then(res => {
    console.log(res)
  })

  // 打印结果
  /*
  [
    {status: 'fulfilished', value: 1},
    {status: 'fulfilished', value: 2},
    {status: 'fulfilished', value: 3}
  ]
  */

  // 添加一个reject
  const promise4 = Promise.resolve(1)
  const promise5 = Promise.reject('reject')
  const promise6 = Promise.resolve(3)

  Promise.allSettled([promise4, promise5,promise6]).then(res => {
    console.log(res, 'resolve')
  }).catch(e => {
    console.log(e)
  })

  // 打印结果
   /*
  [
    {status: 'fulfilished', value: 1},
    {status: 'rejected', value: 'reject'},
    {status: 'fulfilished', value: 3}
  ]
  */

可以看出来allSettled和all最大的区别就是,allSettled不管是resolve,还是reject都能完整返回结果数组,只不过每个数组项都是以对象的形式输出,status描述状态,value接收返回值。

实现MyPromise.allSettled

allSettled的整体逻辑跟all是一样的,只不过返回值的处理稍微有所不同

  class MyPromise {
// ...
  static allSettled(promises) {
      // 已然是返回一个promise
      return new MyPromise((resolve, reject) => {
        // 创建一个收集返回值的数组
        const result = []

        // 执行
        deepPromise(promises[0], 0 , result)

        // 返回结果
        resolve(result)

        // 这里我们用递归来实现
        // @param {MyPromise} promise 每一个promise方法
        // @param {number} index 索引
        // @param {string[]} result 收集返回结果的数组
        function deepPromise(promise, index, result) {
          // 边界判断
          // 所有执行完之后返回收集数组
          if(index > promises.length - 1) {
            return result
          }

          if(typeof promise.then === 'function') {
            // 如果是promise
            promise.then(res => {
              index++
              result.push({status: 'fulfilished', value: res}) // 这里推入的是对象
              deepPromise(promises[index], index, result)
            }).catch(e => {
              // reject直接返回
              index ++
              result.push({status: 'rejected', value: res}) // 这里推入的是对象
              deepPromise(promises[index], index, result)
            })
          }else {
            // 如果是普通值
            // 这里我们只做简单判断,非promise则直接当返回值处理
            index++
            result.push({status: 'fulfilished', value: res}) // 这里推入的是对象
            deepPromise(promises[index], index, res)
          }
        }
      })

    }
  // ...
  }

  // 测试
  // 创建三个promise
  const promise1 = MyPromise.resolve(1)
  const promise2 = MyPromise.resolve(2)
  const promise3 = MyPromise.resolve(3)

  MyPromise.allSettled([promise1,promise12,promise3]).then(res => {
    console.log(res)
  })

  // 打印结果
  /*
  [
    {status: 'fulfilished', value: 1},
    {status: 'fulfilished', value: 2},
    {status: 'fulfilished', value: 3}
  ]
  */

  // 添加一个reject
  const promise4 = MyPromise.resolve(1)
  const promise5 = MyPromise.reject('reject')
  const promise6 = MyPromise.resolve(3)

  Promise.allSettled([promise4, promise5,promise6]).then(res => {
    console.log(res, 'resolve')
  }).catch(e => {
    console.log(e)
  })

  // 打印结果
   /*
  [
    {status: 'fulfilished', value: 1},
    {status: 'rejected', value: 'reject'},
    {status: 'fulfilished', value: 3}
  ]
  */

完整代码

class MyPromise {
  constructor(executor) {
    // 初始化状态status
    // 返回值value
    // 错误原因reason
    this.statue = PENDDING;
    this.value = undefined;
    this.reason = undefined;

    // 返回值回调队列和错误回调队列
    this.resolves = [];
    this.rejects = [];

    // 声明resolve函数
    const resolve = (value) => {
      if (this.status === PENDDING) {
        this.status = RESOLVED; // 变更状态为完成状态
        this.value = value; // 赋值

        // 执行resolves队列
        while (this.resolves.length) {
          const callback = this.resolves.shift();
          callback(value);
        }
      }
    };

    // 声明reject函数
    const reject = (reason) => {
      if (this.statue === PENDDING) {
        this.status = REJECTED; // 变更状态为拒绝状态
        this.reason = reason; // 赋值

        // 执行rejects队列
        while (this.rejects.length) {
          const callback = this.rejects.shift();
          callback(reason);
        }
      }
    };
    
    try{
    	executor(resolve,reject)
    }catch(e){
    	reject(e)
    }
  }

  // then
  then(resolve, reject) {
    // 判断resolve和reject未传入的情况,解决空值透传问题
    // then()情况
    typeof resolve !== 'function' ? resolve = value => value : resolve
    typeof reject !== 'function' ? reject = reason => throw new Error(reason instanceof Error ? reason.message : reason): reject 

    //根据规范,then会返回一个全新的promise
    return new MyPromise((resolveFn, rejectFn) => {
        // 重写传入的resolve方法
        // 判断返回值
        const fulfilished = value => {
            try{
                // 接收返回值
                const res = resolve(value)

                // 判断返回值类型:promise或普通类型
                // 如果是promise,则往下执行一次then
                // 如果是普通类型,则直接执行resolveFn,保证value是最新值
                res instanceof MyPromise ? MyPromise.then(resolveFn,rejectFn) : resolveFn(res)
            }catch(e) {
                rejectFn(e)
            }
        }

        // 重写传入的reject方法
        // 判断返回值
        const rejected = reason => {
            try{
        // 接收返回值
                const res = reject(reason)

                // 判断返回值类型:promise或普通类型
                // 如果是promise,则往下执行一次then
                // 如果是普通类型,则直接执行rejectFn,保证value是最新值
                res instanceof MyPromise ? MyPromise.then(resolveFn,rejectFn) : rejectFn(res)
            }catch(e){
                rejectFn(e instanceof Error ? e.message: e)
            }

        }

        // 判断同步异步任务
        // 执行相对应的方法
        // 这里用switch方法改进
        switch(this.status) {
            case RESOLVED:
                fulfilished(this.value)
            break;

            case REJECTED:
                rejected(this.reason)
            break;

            case PENDDING:
                this.resolves.push(fulfilished)
                this.rejects.push(rejected)
              break;
        }
    })

  }
  
  catch(errorFn) {
    // 这里只需注册执行下then,传入callback就能实现
    this.then(null, errorFn);
  }

  // resolve
  static resolve(value) {
    return new MyPromise((resolveFn, rejectFn) => {
      resolveFn(value)
    })
  }

    // reject
  static reject(reason) {
    return new MyPromise((resolveFn, rejectFn) => {
      rejectFn(reason)
    })
  }

  // all
  static all(promises) {
    // 已然是返回一个promise
    return new MyPromise((resolve, reject) => {
      // 创建一个收集返回值的数组
      const result = []

      // 执行
      deepPromise(promises[0], 0 , result)

      // 返回结果
      resolve(result)

      // 这里我们用递归来实现
      // @param {MyPromise} promise 每一个promise方法
      // @param {number} index 索引
      // @param {string[]} result 收集返回结果的数组
      function deepPromise(promise, index, result) {
        // 边界判断
        // 所有执行完之后返回收集数组
        if(index > promises.length - 1) {
          return result
        }

        if(typeof promise.then === 'function') {
          // 如果是promise
          promise.then(res => {
            index++
            result.push(res)
            deepPromise(promises[index], index, result)
          }).catch(e => {
            // reject直接返回
            reject(e instanceof Error ? e.message : e)
          })
        }else {
          // 如果是普通值
          // 这里我们只做简单判断,非promise则直接当返回值处理
          index++
          result.push(promise)
          deepPromise(promises[index], index, res)
        }
      }
    })

  }

  // allSettled
  static allSettled(promises) {
      // 已然是返回一个promise
      return new MyPromise((resolve, reject) => {
        // 创建一个收集返回值的数组
        const result = []

        // 执行
        deepPromise(promises[0], 0 , result)

        // 返回结果
        resolve(result)

        // 这里我们用递归来实现
        // @param {MyPromise} promise 每一个promise方法
        // @param {number} index 索引
        // @param {string[]} result 收集返回结果的数组
        function deepPromise(promise, index, result) {
          // 边界判断
          // 所有执行完之后返回收集数组
          if(index > promises.length - 1) {
            return result
          }

          if(typeof promise.then === 'function') {
            // 如果是promise
            promise.then(res => {
              index++
              result.push({status: 'fulfilished', value: res}) // 这里推入的是对象
              deepPromise(promises[index], index, result)
            }).catch(e => {
              // reject直接返回
              index ++
              result.push({status: 'rejected', value: res}) // 这里推入的是对象
              deepPromise(promises[index], index, result)
            })
          }else {
            // 如果是普通值
            // 这里我们只做简单判断,非promise则直接当返回值处理
            index++
            result.push({status: 'fulfilished', value: res}) // 这里推入的是对象
            deepPromise(promises[index], index, res)
          }
        }
      })

    }
}

总结

至此Promise A+的完整的方法和实现就完成了,个人觉得实现promise的难点在于理解then的值如何处理透传,这一个点能够理解的话,其它方法和逻辑都会比较顺其自然,有问题欢迎各位大佬在评论处指出。