认识Promise和手写Promise

1,018 阅读24分钟

1.前言:我们为什么要使用Promise

在es6之前,我们为了获取异步结果的时候,往往采用的是回调函数形式,即某个内部含有异步操作的函数,我们在调用该函数之前,传递回调函数作为参数,当我们异步拿到结果时,在内部调用这个回调函数,并把结果传递出来,实现异步回调。

我们可以看下下面的例子:

// request.js :接口的封装
function requestData(url, successCallback, failtureCallback) {
  // 模拟网络请求
  setTimeout(() => {
    // 拿到请求的结果
    // url传入的是ali, 请求成功
    if (url === "ali") {
      // 成功
      let names = ["1", "2", "3"]
      successCallback(names)
    } else { // 否则请求失败
      // 失败
      let errMessage = "请求失败, url错误"
      failtureCallback(errMessage)
    }
  }, 3000);
}

// main.js :调用
requestData("ali", (res) => {
  console.log(res)
}, (err) => {
  console.log(err)
})

分析:

  1. 为了获取接口的异步返回值,我们向封装接口的方法传递了额外的successCallbackfailtureCallback这两个回调函数参数;
  2. 请求成功执行successCallback回调,传递成功的值,即res,失败回调亦是如此,拿到err; 从表明上看,我们并没有发现这种异步回调有什么不好,那么为什么要放弃呢?

首先,该方法是我们自己封装的,就有必要去设计好回调函数的名称,并且要自己去调用;

其次,回调函数的可读性并不太好,特别是当处理多个异步嵌套的时候,我们就需要传递很多个回调函数,俗称回调地狱

最后,如果是第三方库使用异步回调,我们甚至需要去看源码或者文档,去了解什么时候才能获取到这个结果等等。

所以,我们需要放弃这种异步回调的方式,正确的来说,是我们需要在自己的开发过程中避免使用这种异步回调,而让这种回调的过程封装到一个标准中,我们只需要按指定的规范去传递回调函数,就能拿到结果,而不必自己去封装去处理这个过程。一万个人有一万个哈姆雷特,正因为如此,我们更应该选择唯一的标准去解决,这个标准就是Promise

2.说明

Promise(详看MDN)遵循 Promise A+规范译文),但同es6的Promise一样,我们实现的Promise也在该规范基础上扩展了catchfinally方法。

本文并不会手写完整的Promise,例如返回值直接是Promise对象和返回值是带有then方法的对象等,并没有将这些边缘情况考虑进去。读者如果有兴趣,可自行添加。

另外,本文会分步去介绍自定义Promise从零到完成的整个过程,并会较为详细的说明各步骤的原因,尽量做到步步为营,同时对于需要注意的地方也会特别声明。

那么,就开始我们的Promise之旅吧!

3.promise的结构搭建

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled) : 意味着操作成功完成。
  • 已拒绝(rejected) : 意味着操作失败。

Promise详情请至上面提供的MDN地址查看,此处不再过多描述什么是Promise及如何使用。

我们先简单看下es6中使用Promise的例子,从结果去推结构,从而搭建自己的Promise

例子:

//成功状态:
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  });
})

promise1.then(
  (res) => {
    console.log("res:", res); //res: 111
  },
  (err) => {
    console.log("err:", err);
  }
);

//拒绝状态:
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(222);
  });
})

promise2.then(
  (res) => {
    console.log("res:", res);
  },
  (err) => {
    console.log("err:", err); //err: 222
  }
);

分析:

  1. Promise是一个类;
  2. 实例化的时候,会直接执行传递的回调函数executor
  3. 回调函数executor有两个函数作为参数,分别是reslovereject
  4. 异步处理成功后,只需把需要传递的结果作为参数,执行reslove函数,就会调用then中第一个回调函数,res就是传递的成功结果;
  5. 异步处理失败后,只需把需要传递的结果作为参数,执行reject函数,就会调用then中第二个回调函数,err就是传递的失败结果;
  6. Promise的状态一旦确定,就不可更改,初始化的时候状态为pending,当执行resolve()时把状态改成了fulfilled,执行reject()时改成了rejected
  7. then函数的返回值是新的Promise对象(具体后面链式调用会讲); 像下面这种情况,由于reslove(111)已经把pending改变成了fulfilled,因此下面继续执行reject(22)是无效的,该代码并不会执行,所以then中第二个函数参数也不会被执行。
//成功状态:
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
    reject(222);
  });
})

promise.then(
  (res) => {
    console.log("res:", res); //res: 111
  },
  (err) => {
    console.log("err:", err);
  }
);

通过上面几点的分析,我们对Promise的结构已经有了大概的认知,下面我们就搭建我们自定Promise的结构。

// 定义Promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";

// 创建自定义Promise的类
class ALPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING; //初始化pending状态
    this.value = undefined;
    this.reason = undefined;
    
    //执行成功:
    const resolve = (value) => {
      // 保证Promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        //(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
        queueMicrotask(() => {
            //防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PEDDING判断
          if (this.status !== PROMISE_STATUS_PENDING) return; 
          this.onFulfilled(value);
          console.log("执行了resolve", value);
        });
      }
    };
    
    //执行拒绝:
    const reject = (reason) => {
      // 保证Promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        //(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
        queueMicrotask(() => {
            //防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PENDING判断
          if (this.status !== PROMISE_STATUS_PENDING) return; 
          console.log("执行了reject", reason);
          this.onRejected(reason);
        });
      }
    };
    //执行executor回调函数
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

//创建Promise对象
const alPromise = new ALPromise((resolve, reject) => {
  resolve(111);
  // reject(222);
})
// 调用then方法
alPromise.then(
  (res) => {
    console.log("res:", res); // 111
  },
  (err) => {
    console.log("err:", err);
  }
);

以上是自定义Promise的基本结构,这里我们并没有实现then返回值改成新的Promise对象,因为还未涉及到链式调用,等后面讲到链式调用,我们再详细改造。

总之,正如我们看到的,Promise的基本结构,还是还是比较简单的,无非就是四点:

  1. 初始化执行executor函数;
  2. 执行then函数时收集回调函数;
  3. 调用resolvereject函数时改变Promise状态;
  4. 调用resolvereject函数时手动分别(异步)执行then中的第一个和第二个回调函数,并传递值;

4.Promise中then多次调用及异步调用情况

Promise并没有限制我们调用then方法的次数,我们可以同时调用多次,所以我们需要对之前搭建的结构进行改造,从而实现可以多次调用。

例子如下:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  });
})

promise1.then(
  (res) => {
    console.log("res:", res); //res: 111
  },
  (err) => {
    console.log("err:", err);
  }
);

promise1.then(
  (res) => {
    console.log("res:", res); //res: 111
  },
  (err) => {
    console.log("err:", err);
  }
);

// 异步调用(1秒后输出)
setTimeout(() => {
  promise1.then(
    (res) => {
      console.log("res:", res); // res:111
    },
    (err) => {
      console.log("err:", err);
    }
  );
}, 1000);

分析:

  1. 执行then函数时,收集到的多个成功或者失败的回调函数存储在数组中;
  2. 执行resolvereject函数时遍历并执行上面收集到的成功或失败数组中的所有函数;
  3. 异步调用时,如果我们按照之前的方式,由于定时器是宏任务,那么必然是数组中回调函数全部执行完毕,才会把新的异步回调函数追加到数组中,那么就会导致该回调不会执行,因此当追加进数组之前如果状态已经确定,就不要追加到数组,而是直接执行; 因此,对原结构代码进行改造,如下:
// 定义Promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";

// 创建自定义Promise的类
class ALPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING; //初始化pending状态
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFn = []; // 存储多个成功的回调函数
    this.onRejectedFn = []; //存储多个失败的回调函数
    //执行成功:
    const resolve = (value) => {
      // 保证Promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        //(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
        queueMicrotask(() => {
            //防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PENDING判断
          if (this.status !== PROMISE_STATUS_PENDING) return; 
          this.onFulfilled(value);
          console.log("执行了resolve", value);
        });
      }
    };
    
    //执行拒绝:
    const reject = (reason) => {
      // 保证Promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        //(使用异步微任务,保证then中回调函数已经加载)使用微任务queueMicrotask进行异步调用
        queueMicrotask(() => {
            //防止同时写了resolve和reject,异步原因都通过了PROMISE_STATUS_PENDING判断
          if (this.status !== PROMISE_STATUS_PENDING) return; 
          console.log("执行了reject", reason);
          this.onRejected(reason);
        });
      }
    };
    //执行executor回调函数
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
      // 1.如果在then调用的时候, 状态已经确定下来
    if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
      onFulfilled(this.value)
    }
    if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
      onRejected(this.reason)
    }

    // 2.考虑到多个执行,存储到数组中
    this.onFulfilledFn.push(onFulfilled);
    this.onRejectedFn.push(onRejected);

  }
}

//创建Promise对象
const alPromise = new ALPromise((resolve, reject) => {
  // resolve(111);
  reject(222);
})
// 调用then方法
alPromise.then(
  (res) => {
    console.log("res:", res); 
  },
  (err) => {
    console.log("err:", err); // 222
  }
);

alPromise.then(
  (res) => {
    console.log("res:", res);
  },
  (err) => {
    console.log("err:", err); // 222
  }
);

可以看到,我们只是把回调函数改成了用数组存储,调用的时候遍历数组中每一个回调函数,就实现了then的多次调用功能。

5.Promise的then链式调用

Promise中最难的部分就是关于then链式调用的实现,前面我们有提到then函数的返回值的一个Promise对象,在这里我们就会对上面的代码进行改造。then链式调用的难点在于怎么去实现多个Promise中数据的传递。

我们先看下例子:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  });
});
promise1
  .then(
    (res) => {
      console.log("res1:", res); // res1:111
    },
    (err) => {
      console.log("err1:", err);
    }
  )
  .then(
    (res) => {
      console.log("res2:", res); // res2:undefined
    },
    (err) => {
      console.log("err2:", err);
    }
  );

分析: (我们可以试着用我们上面自定义实现的Promise来分析用这个例子中的代码逻辑)

  1. 执行promise1.then()的时候,内部会把then中两个回调函数存储到对应状态的数组中;

  2. 异步执行resolve(111)时,调用了onFulfilledFn数组中的回调函数,此处只有这一个,该函数没有返回值,我们知道没有返回值的函数其实内部默认是返回undefined的;

    (res) => { console.log("res1:", res)}
    
  3. 由于状态一旦确定就不会改变,所以第二个回调函数(err) => {console.log("err2:", err)}是不会被执行的; (下面过程因为我们自定义Promise未实现,因此直接描述实际Promise的逻辑,根据这个逻辑的过程,回头在我们自定义的Promise中实现)

  4. 经过上面的步骤,我们知道then函数已经执行完毕了,我们之前说过then函数其实是返回一个Promise对象的,当然这是Promise内部进行处理的。同时我们获取到了第一个回调函数中的返回值,该例子中返回值是undefined,这个返回值会被当做新Promise调用resolve的参数,即resolve(返回值),相当于如下:

    return new Promise((resolve,reject)=>{
        resolve(undefined)
    })
    
  5. 我们回想一下之前自定义的Promise,这个resolve()函数是干嘛的呢?没错,执行这个resolve()的时候,其实就是执行了存在onFulfilledFn数组中的所有回调函数,即then中的第一个回调函数参数,当然此处这个then是第一个then返回的新的Promise后面紧接的then,即:

    新的Promise对象.then(
    (res) => {
      console.log("res2:", res); // res2:undefined
    },
    (err) => {
      console.log("err2:", err);
    }
    
  6. 那么很显然resolve(undefined)中的undefined最终会传递到上面这个then第一个回调函数的res中,因此上面的打印结果才是res2:undefined; 从上面的分析,我们已经大致了解到了Promisethen链式调用的经历过程,当然我们就写了一个例子,而实际情况却有多种,下面我们把大致会出现的情况都罗列出来。

情况:

  1. 该情况忽略:第一个then回调函数返回值直接就是Promise对象或是带有then方法的对象,由于说明中我们声明了暂时不实现这两种情况,因此排除在外,读者可自行了解这两种情况是如何确定状态的,有兴趣可自行在最终我们实现的Promise上补充实现;
  2. 其余情况: | 顶层Promise对象状态 | 第一个then回调 | then返回Promise的状态 | 第二个then回调 | | --- | --- | --- | --- | | fulfilled| 第一个函数参数执行|fulfilled | 第一个函数参数执行 | fulfilled| 第一个函数参数执行抛出异常|fulfilled | 第二个函数参数执行 | rejected| 第二个函数参数执行|fulfilled | 第一个函数参数执行 | rejected| 第二个函数参数执行抛出异常|rejected | 第二个函数参数执行

正常来说我们如果不手动设置【回调函数返回值直接就是Promise对象、带有then方法的对象、抛出错误】,而只是返回普通的数据的情况,那么在这条then链中,只要顶部Promise状态固定了,下面所有的新的Promise状态都是确定的,例如:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  });
});
promise1
  .then(
    (res) => {
      console.log("res1:", res); // res1: 111
    },
    (err) => {
      console.log("err1:", err);
    }
  )
  .then(
    (res) => {
      console.log("res2:", res); // res2: undefined
    },
    (err) => {
      console.log("err2:", err);
    }
  )
  .then(
    (res) => {
      console.log("res3:", res); // res3: undefined
    },
    (err) => {
      console.log("err3:", err);
    }
  )
  .then(
    (res) => {
      console.log("res4:", res); // res4: undefined
    },
    (err) => {
      console.log("err4:", err);
    }
  );

或者

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(222);
  });
});
promise1
  .then(
    (res) => {
      console.log("res1:", res); 
    },
    (err) => {
      console.log("err1:", err); // err1:222
    }
  )
  .then(
    (res) => {
      console.log("res2:", res); // res2: undefined
    },
    (err) => {
      console.log("err2:", err);
    }
  )
  .then(
    (res) => {
      console.log("res3:", res); // res3: undefined
    },
    (err) => {
      console.log("err3:", err);
    }
  )
  .then(
    (res) => {
      console.log("res4:", res); // res4: undefined
    },
    (err) => {
      console.log("err4:", err);
    }
  );

我们知道,除了第一个Promise因为是我们手动控制resolve()reject()改变状态,下面的都是fulfilled状态,都会执行then中第一个回调函数,并拿到上一个then的返回值作为回调函数的参数;

如果是【回调函数返回值直接就是Promise对象、带有then方法的对象】情况,就可以手动在里面去调用resolve()reject()改变状态,当然我们前面提到了,此次暂时不写这两种情况;

还有就是【抛出错误(包括代码写错的报错)】的情况,抛出错误的地方会中断执行,同时回调下一个then的第二个回调函数,即新的Promise的状态为rejected

综上,我们改造原来的代码,把then的链式调用加上。

// 定义promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";

// 工具函数:封装处理错误捕获的公共代码方法
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
  try {
    const result = execFuc(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}

// 创建自定义promise的类
class ALPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFn = [];
    this.onRejectedFn = [];

    const resolve = (value) => {
      // 保证promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        // 使用微任务queueMicrotask进行异步调用,而不使用宏任务的定时器
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          // console.log("执行了resolve", value);
          this.status = PROMISE_STATUS_FULFILLED;
          this.onFulfilledFn.forEach((fn) => fn(value));
        });
      }
    };
    const reject = (reason) => {
      // 保证promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          // console.log("执行了reject", reason);
          this.status = PROMISE_STATUS_REJECTED;
          this.onRejectedFn.forEach((fn) => fn(reason));
        });
      }
    };
    // 捕获外面的错误
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    // 返回promise对象
    return new ALPromise((resolve, reject) => {
      // 此处最好不要使用普通函数来作为executor的回调函数,因为内部的this会指向顶层作用域,此处用es6模块,顶层为自动开启严格模式的undefined
      // 情况1:考虑到执行then的回调之前,promise的状态已经确定了,则不需要追加到下面数组,直接执行
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        // try {
        //   const value = onFulfilled(this.value);
        //   resolve(value);
        // } catch (err) {
        //   reject(err);
        // }
        execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        // try {
        // 	const reason = onRejected(this.reason);
        //   resolve(reason);
        // } catch (err) {
        // 	reject(err);
        // }
        execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
      }

      // 情况2:执行then回调时还未确定状态,考虑到多个执行,存储到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFn.push(() => {
          // try {
          //   const value = onFulfilled(this.value);
          //   resolve(value);
          // } catch (err) {
          //   reject(err);
          // }
          if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedFn.push(() => {
          // try {
          //   const reason = onRejected(this.reason);
          //   resolve(reason);
          // } catch (err) {
          //   reject(err);
          // }
          if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
}

const alPromise = new ALPromise((resolve, reject) => {
  resolve(111);
  // reject(222);
  // throw new Error("Error Message");
});

// 链式调用
alPromise
  .then(
    (res) => {
      console.log("res1:", res); // res1:111
      // throw new Error("err message");
      return "aaa";
    },
    (err) => {
      console.log("err1:", err);
      // throw new Error("err message");
      return "bbb";
    }
  )
  .then(
    (res) => console.log("res2:", res), // res2:aaa
    (err) => console.log("err2", err)
  );

以上,我们便完成了Promisethen的链式调用。

6. Promise的catch实现

catch方法不属于Promise A+规范中的,但是是属于es6中新增的方法,针对then中需要传递第二个参数去获取rejected状态返回值的一种可读性较强、较美观的写法,因此我们在自定义Promise也中实现它吧。 看例子:

const promise = new Promise((resolve, reject) => {
  reject("111");
})

promise.then(res => {
  console.log("res:", res);
}).catch(err => {
  console.log("err:", err); // err:111
  return "catch value";
}).then(res => {
  console.log("res:", res); // res:catch value
}).catch(err => {
  console.log("err:", err);
})

分析:

  1. catch是为了捕获失败/错误的情况的,相当于原then不传第二个回调函数参数,而用catch方法去接收这个回调函数参数,那么我们是不是可以理解catch就是第二个then,但是第一个参数onFulfilled设置为undefined,第二个参数依旧为onRejected回调函数,那么就把catch的问题转换为then的链式调用了;
  2. 所以当需要使用catch捕获错误时,我们只需要把第一个then中第二个拒绝状态onRejected回调函数的err传递到下一个thenonRejected中,相当于原本在第一个then的第二个拒绝状态的onRejected回调函数处理的err转移到下一个then的第二个拒绝状态的onRejected回调函数中处理;
  3. 第一个then不传递第二个参数,我们知道,如果没有第二个参数onRejected,当Promise状态为rejected时,就不能回调onRejected并传递err,因此我们需要想办法设置一个默认的onRejected,从而实现执行onRejected回调,并且把err传递给下一个Promise对象的catch中,即本质上是then的第二个参数onRejected中;
  4. 5的链式调用我们知道,正常情况下,除了顶层Promise外,其余上个Promise对象不论是什么状态,都会执行then中的第一个回调函数onFulfilled,而不会传递到第二个回调函数onRejected中,所以我们采用抛出异常的方式,刚好这种方式可以实现;
  5. 最后,catch方法也返回一个新的Promise对象,目的是为了继续扩展链结构(finally方法);

根据上述分析,我们对代码进行改造,如下:

// 定义promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";

// 工具函数:封装处理错误捕获的公共代码方法
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
  try {
    const result = execFuc(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}

// 创建自定义promise的类
class ALPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFn = [];
    this.onRejectedFn = [];

    const resolve = (value) => {
      // 保证promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        // 使用微任务queueMicrotask进行异步调用,而不使用宏任务的定时器
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.onFulfilledFn.forEach((fn) => fn(value));
        });
      }
    };
    const reject = (reason) => {
      // 保证promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.onRejectedFn.forEach((fn) => fn(reason));
        });
      }
    };
    // 捕获外面的错误
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    //当没有onRejected回调函数时,手动改成默认抛出错误的回调函数
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;

    // 返回promise对象
    return new ALPromise((resolve, reject) => {
      // 情况1:考虑到执行then的回调之前,promise的状态已经确定了,则不需要追加到下面数组,直接执行
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
      }

      // 情况2:执行then回调时还未确定状态,考虑到多个执行,存储到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFn.push(() => {
          if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedFn.push(() => {
          if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
  //定义catch方法:捕获异常
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
}

const alPromise = new ALPromise((resolve, reject) => {
  resolve(111);
  // reject(222);
  // throw new Error("Error Message");
});

// 链式调用:
alPromise
  .then((res) => {
    console.log("res1:", res); // res1:111
    throw new Error("err1 message");
    return "aaa";
  })
  .then((res) => {
    // console.log("res2:", res);
    // throw new Error("err2 message");
    return "bbb";
  })
  .catch((err) => {
    console.log("err:", err); // err:报错信息
  });

7.实现finally方法

finally方法同样不属于Promise A+规范中的,但是是属于es6中新增的方法,不论Promise最终确定为哪种状态(fulfilled或rejected),都会执行finally函数传递的回调函数。 看例子:

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

promise.then(res => {
  console.log("res:", res);
}).catch(err => {
  console.log("err:", err); // reject message
}).finally(() => {
  console.log("finally code execute"); // finally code execute
})

分析:

  1. 不论最终状态确定为 fulfilled还是rejected,都会执行finally传递的回调函数参数;
  2. 我们知道,当Promise状态为fulfilled时,会执行then的第一个回调函数onFulfilled,并传递过来参数,当Promise状态为rejected时,会执行then的第二个回调函数onRejected或者执行catch的回调函数参数,那么我们可以理解为finally就是then链的继续延伸,我们在执行catch内部时就可以设计调用then方法,接收回调函数参数,然后在调用onFulfilledonRejected时都执行该回调函数即可;
  3. 最后我们考虑一个问题,我们在设计catch时,调用then传递的第一个参数是undefined,当时是因为catch捕获错误时,只会执行第二个回调函数onRejected参数,但是在这里就会出现问题;
  4. 当这条链上同时出现catchfinally时,如果Promise的状态是onFulfilled时,必然不会走catch调用then的第二个回调函数,而是走内部第一个的undefined,而undefined等同于不存在回调函数,无法通过if判断,导致不会执行resolve,无法将当前Promise的状态改为fulfilled,会一直处于pending状态,所以也就不会去调用finally传递的回调函数;
  5. 所以我们就需要判断,当onFulfilled回调函数不存在时,则创建一个默认的回调函数,在这个回调函数中直接返回原本调用该函数传递来的参数即可; 通过上面的分析,我们来实现下带有finally功能的自定义Promise
// 定义promise的三种状态
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";

// 工具函数:封装处理错误捕获的公共代码方法
function execFucntionWithCatchError(execFuc, value, resolve, reject) {
  try {
    const result = execFuc(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}

// 创建自定义promise的类
class ALPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFn = [];
    this.onRejectedFn = [];

    const resolve = (value) => {
      // 保证promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        // 使用微任务queueMicrotask进行异步调用,而不使用宏任务的定时器
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.onFulfilledFn.forEach((fn) => fn(value));
        });
      }
    };
    const reject = (reason) => {
      // 保证promise状态确定后不可变
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.onRejectedFn.forEach((fn) => fn(reason));
        });
      }
    };
    // 捕获外面的错误
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    //解决finally:当没有onFulfilled回调函数时,手动改成默认返回调用该函数传递来的参数
    const defaultOnResolve = (value) => {
      return value;
    };
    onFulfilled = onFulfilled || defaultOnResolve;
    
   //解决catch;当没有onRejected回调函数时,手动改成默认抛出错误的回调函数
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;

    // 返回promise对象
    return new ALPromise((resolve, reject) => {
      // 情况1:考虑到执行then的回调之前,promise的状态已经确定了,则不需要追加到下面数组,直接执行
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
      }

      // 情况2:执行then回调时还未确定状态,考虑到多个执行,存储到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFn.push(() => {
          if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedFn.push(() => {
          if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
   //定义catch方法:捕获异常
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
  // 定义finally方法:两种状态都需要执行
  finally(onFinally) {
    // console.log(222222);
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
}

const alPromise = new ALPromise((resolve, reject) => {
  resolve(111);
  // reject(222);
  // throw new Error("Error Message");
});

// 链式调用
alPromise
  .then((res) => {
    console.log("res1:", res); // res1:111
    // throw new Error("err1 message");
    return "aaa";
  })
  .then((res) => {
    console.log("res2:", res); // res2:aaa
    // throw new Error("err2 message");
    return "bbb";
  })
  .catch((err) => {
    console.log("err:", err);
    return "ccc";
  })
  .finally(() => {
    console.log("这是finally"); // 这是finally
  });

以上就是关于finally方法的补充。

综上,我们已经把所有Promise实例对象上的方法完善好了,可以说整个Promise的手写基本算是完成了,其中比较难的地方就是then方法,其余像catchfinally,都是调用then方法。

下面封装几个Promise的类方法。

8. 类方法:resolve和reject的实现

resolvereject方法比较简单,此处不过多阐述。

class ALPromise{
  ......
  static resolve(value) {
    return new ALPromise((resolve) => {
      resolve(value);
    });
  }
  static reject(reason){
    return new ALPromise((resolve,reject) => {
      reject(reason);
    });
  }
  ......
}

ALPromise.resolve("Hello World").then(res => {
  console.log("res:", res); // Hello World
})

ALPromise.reject("Error Message").catch(err => {
  console.log("err:", err); // Error Message
})

9.类方法:all和allSettled的实现

注意:并没有做关于传入的数组中,元素是普通数据的情况。

Promise.all() 方法接收一个promiseiterable类型(注:ArrayMapSet都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promiseresolve回调的结果是一个数组。这个Promiseresolve回调执行是在所有输入的promiseresolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promisereject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

......
// 上面省略ALPromise类的声明
  static all(promises) {
    return new ALPromise((resolve, reject) => {
      const values = new Array(promises.length);
      let i = 0; //标识都追加进数组
      promises.forEach((promise, index) => {
        promise.then(
          (res) => {
            values[index] = res;
            i++;
            if (i === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }
  
  static allSettled(promises) {
    return new ALPromise((resolve, reject) => {
      const values = new Array(promises.length);
      let i = 0; //标识都追加进数组
      promises.forEach((promise, index) => {
        promise.then(
          (res) => {
            values[index] = { status: PROMISE_STATUS_FULFILLED, value: res };
            i++;
            if (i === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            values[index] = { status: PROMISE_STATUS_REJECTED, value: err };
            i++;
            if (i === promises.length) {
              reject(values);
            }
          }
        )
      })
    })
  }

10.类方法:race和any的实现

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝,即谁先出结果返回谁。

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。

......
// 上面省略ALPromise类的声明
  static race(promises) {
    return new ALPromise((resolve, reject) => {
      promises.forEach((promise) => {
        // 原版
        // promise.then(
        //   (res) => {
        //     resolve(res);
        //   },
        //   (err) => {
        //     reject(err);
        //   }
        // );
        // 简化
        promise.then(resolve, reject);
      });
    });
  }
  
  static any(promises) {
    return new ALPromise((reslove, reject) => {
      const reasons = [];
      promises.forEach(
        (promise) => {
          promise.then((res) => {
            reslove(res);
          });
        },
        (err) => {
          reasons.push(err);
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons));
          }
        }
      );
    });
  }

11.总结

以上就是关于手写Promise的所有内容,本文只是针对Promise基本的构建及常用方法等进行的撰写,旨在一方面是了解Promise内部的处理逻辑,当我们使用Promise的时候能更加清晰的知道所做的每一步操作本质是在做哪些事情;另一方面是锻炼复杂的回调函数逻辑,能让我们在阅读理解和封装自己的需求代码时提供一些开拓的思路。

当然,本文还有很多不足之处,比如很多边界情况,比如某些返回值类型的延伸等等,读者有兴趣可自行拓展;

读者如有发现本文有描述不对、不足的地方,欢迎提出宝贵的建议,不吝感谢!

另附完整的自定义Promise代码(注释已移除):

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "resolved";
const PROMISE_STATUS_REJECTED = "rejected";

function execFucntionWithCatchError(execFuc, value, resolve, reject) {
  try {
    const result = execFuc(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}

class ALPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFn = [];
    this.onRejectedFn = [];

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.onFulfilledFn.forEach((fn) => fn(value));
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.onRejectedFn.forEach((fn) => fn(reason));
        });
      }
    };
    
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;
 
    const defaultOnResolve = (value) => {
      return value;
    };
    onFulfilled = onFulfilled || defaultOnResolve;

    return new ALPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
      }

      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFn.push(() => {
          if (onFulfilled) execFucntionWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedFn.push(() => {
          if (onRejected) execFucntionWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
  catch(onRejected) {
    return this.then(undefined, onRejected);
  } 
  
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
  static resolve(value) {
    return new ALPromise((resolve) => {
      resolve(value);
    });
  }
  static reject(reason) {
    return new ALPromise((resolve, reject) => {
      reject(reason);
    });
  }

  static all(promises) {
    return new ALPromise((resolve, reject) => {
      const values = new Array(promises.length);
      let i = 0;
      promises.forEach((promise, index) => {
        promise.then(
          (res) => {
            values[index] = res;
            i++;
            if (i === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }
  static allSettled(promises) {
    return new ALPromise((resolve, reject) => {
      const values = new Array(promises.length);
      let i = 0;
      promises.forEach((promise, index) => {
        promise.then(
          (res) => {
            values[index] = { status: PROMISE_STATUS_FULFILLED, value: res };
            i++;
            if (i === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            values[index] = { status: PROMISE_STATUS_REJECTED, value: err };
            i++;
            if (i === promises.length) {
              reject(values);
            }
          }
        );
      });
      return values;
    });
  }

  static race(promises) {
    return new ALPromise((resolve, reject) => {
      promises.forEach((promise) => 
        promise.then(resolve, reject);
      });
    });
  }

  static any(promises) {
    return new ALPromise((reslove, reject) => {
      const reasons = [];
      promises.forEach(
        (promise) => {
          promise.then((res) => {
            reslove(res);
          });
        },
        (err) => {
          reasons.push(err);
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons));
          }
        }
      );
    });
  }
}