Promise及其API源码的实现思考过程

1、Promise&&then

我们知道,promise是为了解决异步回调地狱而出现的新的解决方案,它是主要特点是可以通过then拿到异步的结果,then又能返回新的异步结果,形成链式调用

javascript 复制代码
new Promise((resolve,reject)=>{
resolve('结果')
}).then(res=>{
return res
}).then(...)

我们也知道,在promise中有三个状态,pending进行中,fulfilled已完成,rejected已失败,且一旦pending改变,不会再被更改。

所以先根据以上信息,实现:可以通过then链式调用

ini 复制代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        // this.onFulfilledCbs = [];
        // this.onRejectedCbs = [];

        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value
                // this.onFulfilledCbs.forEach(cb => cb(value))
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason
                // this.onRejectedCbs.forEach(cb => cb(reason))
            }
        }

        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        return new myPromise((resolve, reject) => {
            let result = undefined;
            if (this.status === FULFILLED) {
                result = onFulfilled(this.value)
                resolve(result)
            }
            if (this.status === REJECTED) {
                result = onRejected(this.reason)
                reject(result)
            }
            // if (this.status === PENDING) {
            //     this.onFulfilledCbs.push(onFulfilled)
            //     this.onRejectedCbs.push(onRejected)
            // }
        })
    }
}
new myPromise((resolve, reject) => {
    // setTimeout(() => {
        resolve(111)
    // }, 1000)
}).then(res => {
    console.log('res', res)
})

根据以上代码,在then中再return promise,实现了简单的链式调用,且需要setTimeout模拟异步,在then中拿到异步操作返回的结果,因为是异步的,当执行then时status还是pending,需要把成功和失败回调存储,等到resolve或reject时拿出来执行。(这部分代码实现把以上注释部分取消即可。)

根据promise的约定,resolve接收的参数可以有4种,所以需要兼容一下,目前只有一种就是基础值,还有promise、thenable对象、空,且resolve出的值不能是promise自身,否则会造成无限循环引用。

同时用setTimeout将异步模拟一下,有如以下代码:

ini 复制代码
// promise有三种状态,分别是pending.FULFILLED ,rejected,pending是初始状态,FULFILLED 是成功状态,rejected是失败状态
// 状态一旦改变,不会再变
const PENDING = "pending";
const FULFILLED = "FULFILLED ";
const REJECTED = "rejected";
class Promise1 {
  constructor(excutor) {
    // 其实resolve,reject这两个函数不是他们传来的,只是他们调用的内部的方法,它们传来的只是值
    this.status = PENDING;
    this.value = undefined;
    this.error = undefined;
    this.onFulfilledCallbacks = []; // 存储then方法中onFULFILLED 的回调函数
    this.onRejectedCallbacks = []; // 存储then方法中onRejected的回调函数

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach((callback) => callback());
      }
    };
    const reject = (error) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.error = error;
        this.onRejectedCallbacks.forEach((callback) => callback());
      }
    };
    try {
      excutor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    // 处理onFULFILLED 不是函数的情况
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    // 处理onRejected不是函数的情况
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (error) => {
            throw error;
          };
    // then最终还是会return一个promise
    const promise = new Promise((resolve, reject) => {
      const handleFulfilled = () => {
        try {
          const result = onFulfilled(this.value);
          resolvePromise(promise, result, resolve, reject);
        } catch (error) {
          reject(error);
        }
      };

      const handleRejected = () => {
        try {
          const result = onRejected(this.error);
          resolvePromise(promise, result, resolve, reject);
        } catch (error) {
          reject(error);
        }
      };

      if (this.status === FULFILLED) {
        // 为什么这里需要setTimeout,因为要模拟promise是异步的
        setTimeout(handleFulfilled, 0);
      } else if (this.status === REJECTED) {
        setTimeout(handleRejected, 0);
      } else {
        this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
        this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
      }
    });
    return promise;
  }
}
function resolvePromise(promise, result, resolve, reject) {
  // 如果promise和result是同一个对象,则抛出TypeError,
  if (promise === result) {
    return reject(new TypeError("Chaining cycle detected for promise"));
  }
  // 如果result是thenable对象
  if (result && (typeof result === "object" || typeof result === "function")) {
    try {
      const then = result.then;
      if (typeof then === "function") {
        // then就是接收两个参数:resolve或reject,然后出结果的函数,直接执行它,但注意this指向,并传参
        then.call(
          this,
          (value) => {
            resolvePromise(promise, value, resolve, reject);
          },
          (reason) => reject(reason)
        );
      } else {
        resolve(result);
      }
    } catch (error) {
      reject(error);
    }
  } else {
    // 否则直接调用resolve
    resolve(result);
  }
}

2、catch

then后面除了可以跟着then,也可以通过catch方法捕获错误,它等同于then中的第二个参数,所以通过调用then,将catch中的回调函数传给then第二个参数即可。

kotlin 复制代码
// 声明在Promise类中
catch(cb){
  return this.then(_,cb)
}

3、finally

Promise的实例方法还有一个叫finally,是不管成功还是失败都会执行的,还是用then方法返回结果,不管成功还是失败结果都resolve返回一个promise,从而catch后面也可继续链式调用。then接收两个参数,成功后的回调,这个回调的参数是成功返回的值,所以finally若想

javascript 复制代码
// 声明在Promise类中
finally(cb){
    return this.then(
        value=>Promise1.resolve(cb()).then(()=>value)
        reason=>Promise1.resolve(cb()).then(()=>reason)
)}

4、resolve

在Promise类上,有resolve,reject,race,all,allSettled等类方法,接下来一一演示,Promise.resolve(1)就是等同于new Resolve(()=>{resolve(1)}),既然如此直接返回后者,再加个判断,如果不是promise才需要包装,是promise直接返回

scss 复制代码
static resolve(value){
 if(value instanceof Promise1){
 return value;
 }
 return new Promise1(()=>resolve(value))
}

5、reject

reject同理可得,Promise.reject('err')就是等同于new Promise1(()=>{reject('err')})

javascript 复制代码
static reject(reason){
return new Promise1((_,reject)=>reject(reason))
}

6、race

race、all、allSettled都是为了解决多个promise同时执行的,race是当其中一个先执行完毕,多个promise实例的执行结果就是这个最先完成的状态。 既然是多个,那参数就是数组,我们只需去遍历,最先执行resolve的,也就是最先改变状态的,后续即使继续遍历,也不再改变状态。

怎么知道它何时resolve呢,因为我们知道then中的回调是在成功时调用的,所以通过Promise.resolve包装,在then中去resolve实例

javascript 复制代码
static race(arr){
 return new Promise1((resolve,reject)=>{
  arr.forEach(pro=>{
   Promise1.resolve(pro).then((value)=>{
     resolve(pro)
   },reason=>{
     reject(reason)
   })
  })
 })
}

7、all

all是多个promise实例,只要有一个失败,结果就是失败,全部成功才算成功。所以要计算成功的次数,数组长度等于成功的个数后,再resolve结果数组。

ini 复制代码
  static all(arr) {
    return new Promise1((resolve, reject) => {
      let res = [];
      let completeCount = 0;
      if (arr.length === 0) {
        resolve(res);
        return;
      }
      arr.forEach((pro) => {
        Promise1.resolve(pro).then(
          (value) => {
            res[completeCount] = value;
            completeCount++;
            if (completeCount === arr.length) {
              resolve(res);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }
}

8、allSettled

allSettled是不管成功还是失败,都会返回结果的。注意不管当前是成功还是失败,当执行到最后一个时,都是resolve出数组结果,且结果数组对象是由status和结果值组成的,如果成功status='fulfilled',失败status='rejected'

ini 复制代码
  static allSettled(promises) {
    return new CustomPromise((resolve, reject) => {
      const results = [];
      let completedCount = 0;
      if (promises.length === 0) {
        resolve(results);
        return;
      }
      promises.forEach((promise, index) => {
        CustomPromise.resolve(promise).then(
          (value) => {
            results[index] = {
              status: "fulfilled",
              value,
            };
            completedCount++;
            if (completedCount === promises.length) {
              resolve(results);
            }
          },
          (reason) => {
            results[index] = {
              status: "rejected",
              reason,
            };
            completedCount++;
            if (completedCount === promises.length) {
              resolve(results);
            }
          }
        );
      });
    });
  }

根据以上,留一个作业,any方法,只要有一个成功,包装实例返回结果fulfilled,或者全部都失败,结果才会是rejected

相关推荐
剪刀石头布啊6 分钟前
var、let、const与闭包、垃圾回收
前端·javascript
剪刀石头布啊8 分钟前
js常见的单例
前端·javascript
剪刀石头布啊8 分钟前
数据口径
前端·后端·程序员
剪刀石头布啊12 分钟前
http状态码大全
前端·后端·程序员
剪刀石头布啊14 分钟前
iframe通信、跨标签通信的常见方案
前端·javascript·html
宇之广曜23 分钟前
搭建 Mock 服务,实现前端自调
前端·mock
yuko093125 分钟前
【手机验证码】+86垂直居中的有趣问题
前端
用户15129054522029 分钟前
Springboot中前端向后端传递数据的几种方式
前端
阿星做前端29 分钟前
如何构建一个自己的 Node.js 模块解析器:node:module 钩子详解
前端·javascript·node.js
用户15129054522033 分钟前
Web Worker:让前端飞起来的隐形引擎
前端