likes
comments
collection
share

JavaScript高级--Promise②(简单手写Promise)

作者站长头像
站长
· 阅读数 9

源码地址:github.com/Liangjiahon…

实现目标

  • 实现一个 MyPromise,包含原生 Promise 的功能和方法,并符合 Promises/A+ 规范
  • 其中 MyPromise 包括 executor(resolve,reject),实例方法 thencatchfinally,类方法 resolverejectallallSettledraceany

结构搭建

  • 搭建 MyPromise 的基本结构,并且实现 executor 函数和状态管理
 class MyPromise {
   constructor(executor) {
     this.status = 'pending';
     this.value = undefined; // 调用reslove的值
     this.reason = undefined;// 调用reject的值
 ​
     // 立即执行executor
     try {
       executor(resolve, reject);
     } catch (err) {
       // 捕获executor中的错误,并执行reject
       reject(err);
     }
   }
 }
  • 由于 executor 函数会立即执行,所以 constructor() 直接调用
  • 使用 try...catch() 是需要捕获 executor 内部出错或抛出异常,捕获后并调用 rejected
 // 如这种情况
 new MyPromise((reslove, reject) => {
   throw new Error()
 })

resolve 和 reject

  • 首先这两个函数是 Promise 的核心方法,用于异步操作处理的结果,并且在 resolvereject 之后,Promise 的状态会发生对应改变,并且是不可逆的
 class MyPromise {
   constructor(executor) {
     ...
     this.onFulfillFns = []; // 接收then方法中传入的成功回调
     this.onRejectFns = []; // 接收then方法中传入的失败回调
 ​
     const resolve = (value) => {
       // 只有在pending状态才能改变Promise的状态
       if (this.status === 'pending') {
         this.status = 'fulfilled';
         this.value = value;
         // 这里执行收集到的成功处理函数
         this.onFulfillFns.forEach((fn) => fn(value));
       }
     };
 ​
     const reject = (reason) => {
       if (this.status === 'pending') {
         this.status = 'rejected';
         this.reason = reason;
         // 这里执行收集到的失败处理函数
         this.onRejectFns.forEach((fn) => fn(reason));
       }
     };
     ...
   }
  • 由于 Promise 中会有异步操作,那么在 pending 状态时,当调用 promise 对象的 then 方法并传入回调,则需要先把回调保存起来,什么时候 resolvereject 才会执行回调
 // 如下所示
 const promise = new MyPromise((resolve, reject) => {
   settimeout(() => resolve(), 10000) // 10秒后resovle
 })
 ​
 // 这时候promise还是pending状态,其回调要在resolve或reject后才会执行
 // 所以要将回调加入到数组中保存
 promise.then((res) => {...}) 
 promise.then((res) => {...})
 promise.then((res) => {...})
 promise.then((res) => {...})

实例方法

  • Promise 的实例方法有 thencatchfianlly,其中 then 方法才是符合 Promise/A+ 规范的,catchfinallyES6Promise 实现的语法糖

then方法实现

  • then 方法用于为 Promise 实例添加成功和失败的处理回调,接收成功和失败的回调函数,并返回一个新的 Promise,用于链式调用
 then(onFulfilled, onRejected) {
   return new MyPromise((reslove, reject) => { });
 }
  • 首先定义默认的成功和失败的回调函数,当调用 then 方法传入的不是函数时,赋予一个默认的函数
 then(onFulfilled, onRejected) {
   const defOnRejected = (reason) => { throw reason };
   const defonFulfilled = (value) => value;
   
   onFulfilled = isFunc(onFulfilled) ? onFulfilled : defonFulfilled;
   onRejected = isFunc(onRejected) ? onRejected : defOnRejected;
   
   return new MyPromise((reslove, reject) => { });
 }
 ​
  • 其次是要判断 promise 的状态,如果是 fulfilledrejected 状态,直接将回调加入任务队列;如果是 pending 状态,则将回调存放到数组中
 then(onFulfilled, onRejected) {
   // 当不传入第二个回调时,将错误抛出给catch处理
   const defOnRejected = (reason) => { throw reason };
   const defonFulfilled = (value) => value;
   onFulfilled = isFunc(onFulfilled) ? onFulfilled : defonFulfilled;
   onRejected = isFunc(onRejected) ? onRejected : defOnRejected;
 ​
   return new MyPromise((reslove, reject) => {
     const isFulfilled = this.status === PROMISE_STATUS.FULFILLED;
     const isRejected = this.status === PROMISE_STATUS.REJECTED;
     const isPending = this.status === PROMISE_STATUS.PENDING;
     
     // 如果在调用时,promise的状态已经确定,说明此时已经调用了resolve或reject
     // 那么可以直接将回调加入任务队列等待执行
     if (isFulfilled) {
       queueMicrotask(() => execFnWithCatchErr(onFulfilled, this.value, reslove, reject));
     }
     if (isRejected) {
       queueMicrotask(() => execFnWithCatchErr(onRejected, this.reason, reslove, reject));
     }
 ​
     // 如果是pending状态,那么需要将回调添加到数组保存
     if (isPending) {
       this.onFulfillFns.push((res) =>
         queueMicrotask(() => execFnWithCatchErr(onFulfilled, res, reslove, reject))
       );
       this.onRejectFns.push((err) =>
         queueMicrotask(() => execFnWithCatchErr(onRejected, err, reslove, reject))
       );
     } else {
       this.onFulfillFns = [];
       this.onRejectFns = [];
     }
   });
 }
  • 辅助函数 execFnWithCatchErr 实现如下
 const isFunc = (fn) => Object.prototype.toString.call(fn) === '[object Function]';
 const isObj = (o) => Object.prototype.toString.call(o) === '[object Object]';
 ​
 // fn是 --- 成功或失败的回调
 // value是 --- promise成功或失败后的结果
 // resolve和reject是 --- 返回的新Promise的resolve和reject
 const execFnWithCatchErr = (fn, value, resolve, reject) => {
   try {
     // 拿到then方法的返回值
     const result = fn(value);
     // 判断返回值是不是Promise, 或是带then方法的对象
     if (result instanceof MyPromise || (isObj(result) && isFunc(result.then))) {
       result.then(resolve, reject);
     } else {
       // 否则resolve出去
       resolve(result);
     }
   } catch (err) {
     // 如果在回调中抛出了异常,则直接捕获reject
     reject(err);
   }
 };

JavaScript高级--Promise②(简单手写Promise)

catch方法实现

  • catch 方法只是语法糖,只接受一个回调作为参数,相当于 then 方法传入的第二个失败回调
 catch(onRejected) {
   return this.then(undefined, onRejected);
 }
  • 这里让成功的回调为 undefined,调用 catch 方法相当于调用了只传入失败回调的 then 方法

finally方法实现

  • finally 方法也是语法糖,只接受一个回调作为参数,无论是 promise 成功或失败,该方法都会执行,那么就相当于调用了 then 方法,并在传入的回调中执行 finally 的回调
 finally(onFinallyed) {
   return this.then(
     (value) => {
       if (isFunc(onFinallyed)) onFinallyed();
       return value;
     },
     (reason) => {
       if (isFunc(onFinallyed)) onFinallyed();
       throw reason;
     }
   );
 }
  • finally 传入的回调没有参数,也会忽略返回值,所以直接执行即可
  • 由于 finally 方法可以将结果或错误传递给下一个合适的处理程序,相当于调用了 then 方法,并返回传入的参数,让后续的处理方法可以获取
 // 如下所示
 p2.then((res) => 2)
   .finally(() => { console.log('先执行这里的代码') })
   .then(console.log); // 2
  • 相当于以下形式

JavaScript高级--Promise②(简单手写Promise)

类方法

  • Promise 的类方法有 resolverejectallallSettledraceany,都是需要通过Promise.xxx 调用,所以都需要加上 static 关键字

resolve和reject实现

  • resolvereject 都是对传入的参数,使用 new Promise() 进行处理
   static resolve(value) {
     // 判断传入的是否为promise,是则直接返回
     if (value instanceof MyPromise) return value;
     // 如果传入thenable对象,则直接使用对象中的then方法
     if (isObj(value) && isFunc(value.then)) return new MyPromise(value.then);
     return new MyPromise((resolve) => resolve(value));
   }
 ​
   static reject(reason) {
     return new MyPromise((reslove, reject) => reject(reason));
   }
  • resolve 用于创建一个完成的 promise,该 promise 的状态取决于传入的参数;reject 无论如何都是返回一个失败状态的 promise

JavaScript高级--Promise②(简单手写Promise)

all和allSettled实现

  • 这两个方法都是接收一个元素为 promise 的数组,并且返回处理后的结果数组,都是返回一个新 Promise
  • 如果传入的数组带有非 promise 元素,则使用 Promise.resolve进行处理,并且返回结果数组内的顺序,和传入的顺序一致
 static all(promises) {
   return new MyPromise((resolve, reject) => {
     const values = []; // 保存所有结果的数组
     let resolvedCount = 0; // 统计promise完成的个数
 ​
     promises.forEach((promise, index) => {
       // 这里统一使用类方法resolve处理
       MyPromise.resolve(promise).then(
         (value) => {
           // 这里使用传入的顺序,存放对应的结果
           // 如果使用push则顺序会出现差错
           values[index] = value;
           // 完成一个累加1
           resolvedCount++;
           // 使用resolvedCount判断,而不是values.length
           // 如果使用values.length判断,会导致结果数组包含空元素
           if (resolvedCount === promises.length) resolve(values);
         },
         // 只要有异常立马抛出
         (reason) => reject(reason)
       );
     });
   });
 }
 ​
 static allSettled(promises) {
   return new MyPromise((resolve, reject) => {
     const results = []; // 保存所有结果的数组
     let finishedCount = 0; // 统计完成的个数,无论是失败还是成功都算完成
     promises.forEach((promise, index) => {
       MyPromise.resolve(promise).then(
         (value) => {
           results[index] = { status: PROMISE_STATUS.FULFILLED, value };
           finishedCount++;
           if (finishedCount === promises.length) resolve(results);
         },
         (reason) => {
           results[index] = { status: PROMISE_STATUS.REJECTED, reason };
           finishedCount++;
           if (finishedCount === promises.length) resolve(results);
         }
       );
     });
   });
 }
  • 其中 allallSettled 不同的是:all 方法中只要有一个 promise 失败则直接 reject,否则等待所有 promise 完成后才将结果 resolve;而 allSettled 方法不管是失败还是成功都会保存到结果数组中,等待都完成后才 resolve

race和any

  • 这两个方法都是接收一个元素为 promise 的数组,都是返回一个新 Promise
  • race 方法只要有一个 promise 完成,则立即调用新 Promiseresolvereject
  • any 方法是等待第一个成功的 promise 完成后,才调用新 Promiseresolvereject,如果所有promise 都失败了则将错误数组进行 reject,并抛出 AggregateError 错误
 static race(promises) {
   return new MyPromise((resolve, reject) => {
     promises.forEach((promise) => {
       // 使用类方法resolve进行包装,并且只要有完成的直接resolve或reject
       MyPromise.resolve(promise).then(resolve, reject);
     });
   });
 }
 ​
 static any(promises) {
   return new MyPromise((resolve, reject) => {
     const reasons = []; // 存放所有的错误结果
     let rejectedCount = 0; // 计算错误的次数
     promises.forEach((promise, index) => {
       // 使用类方法resolve进行包装,并且只要有完成的直接resolve
       MyPromise.resolve(promise).then(resolve, (reason) => {
         // 如果是错误的promise
         reasons[index] = reason; // 先保存到数组中
         rejectedCount++; // 然后累加错误次数
         // 如果错误次数和传入的promise数组相等,则说明是全都失败了
         if (rejectedCount === promises.length) reject(new AggregateError(reasons));
       });
     });
   });
 }
转载自:https://juejin.cn/post/7278238875449573434
评论
请登录