Skip to content

手写Promise.finally resolve reject catch all

前言

上一篇,根据promise/A+规范手写了promise的核心,只包含peomise原型上的then方法,但是promise还有很多的方法平时经常使用。这一篇就来统统的手写一遍

手写promise github地址

核心代码如下:

js
const STATUS = {
  PADDING: "PADDING", // padding
  FULFILLED: "FULFILLED", // fulfilled
  REJECTED: "REJECTED", // rejected
};

class Promise {
  constructor(executor) {
    this.status = STATUS.PADDING;
    this.value = undefined; 
    this.reason = undefined;

    this.onFulfilledCallBack = []; 
    this.onRejectedCallBack = []; 

    const resolve = (val) => {
      if (this.status === STATUS.PADDING) {
        this.status = STATUS.FULFILLED;
        this.value = val;
        this.onFulfilledCallBack.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.status === STATUS.PADDING) {
        this.status = STATUS.REJECTED;
        this.reason = reason;
        this.onRejectedCallBack.forEach((fn) => fn());
      }
    };
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (data) => data;
    onRejected = typeof onRejected === "function" ? onRejected : (err) => {throw err;};

    let promise2 = new Promise((resolve, reject) => {
      if (this.status === STATUS.FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(x, promise2, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === STATUS.REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(x, promise2, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === STATUS.PADDING) {
        this.onFulfilledCallBack.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(x, promise2, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
        this.onRejectedCallBack.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason); 
              resolvePromise(x, promise2, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }
}
module.exports = Promise;

function resolvePromise(x, promise2, resolve, reject) {
  if (x === promise2) {
    throw TypeError("不允许递归引用同一个promise对象");
  }
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    let called; 
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call( x,  function (y) {
            if (called) return;
            called = true;
            resolvePromise(y, promise2, resolve, reject);
          },
          function (err) {
            if (called) return;
            called = true;
            reject(err);
          }
        );
      } else {
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

Promise.resolve 手写

1. 要点

  1. Promise.resolve是静态方法,默认返回一个成功状态的promise实例,可以调用then方法得到里面的值
  2. 无论Promise.resolve()里面传入普通值还是promise,最终都是返回做一个全新的promise
  3. Promise.resolve里面的方法抛出异常或失败,都会将结果传给then的失败或catch

2. 代码实现

js
static resolve(value) {
  // Promise.resolve会返回一个全新的promise,所以才能then
  return new Promise((resolve, rejevt) => {
    resolve(value); 
  });
}

需要在constroctor里面的resolve方法里面判断一下Promise.resolve里面的value是否是一个promise,若是promise则递归解析

js
const resolve = (val) => {

  //  val有可能是一个promise,这里需要注意处理
  // 若val是一个promise,此时需要调用它的then方法,递归解析
  if(val instanceof Promise) {
    return val.then(resolve,reject) // then是微任务,因此这里可以拿到reject方法
  }

  if (this.status === STATUS.PADDING) {
    this.status = STATUS.FULFILLED;
    this.value = val;
    this.onFulfilledCallBack.forEach((fn) => fn());
  }
};

本节gitHubCommit记录:实现Promise.resolve

catch 和 Promise.reject 手写

1. 要点

  1. catch也是promise私有的方法,catch其实就是失败的then,但是没有成功回调。
  2. 失败的状态没有等待,但是成功是有等待的,这点不一样。
  3. Promise.reject是静态方法,返回的也是一个promise,只是这个promise是失败的promise。

2. 代码实现

2.1 catch的实现

js
// catch接收一个失败的回调
catch(err) {
  return this.then(null, err); // 只有失败,没有成功
}

本节githubCommit记录:catch的实现

2.2 Promise.reject的实现

js
static reject(reason) { 
  return new Promise((resolve, reject) => {
    reject(reason); // 
  });
}

本节githubCommit记录:Promise.reject的实现

finally 手写

声明:==finally==不是try catch finally里面的finally。

1. 要点

  1. finally是promise原型上的方法,无论成功失败都会执行;
  2. 方法接收一个回调函数,回调函数里的参数为undefined,也可以不传回调函数;
  3. filally后面还可以接着then,因为finally返回的也是promise;
  4. 若finally返回失败的promise或抛异常,下一个then一定走失败,且错误是finally里面的错误或者失败原因;
  5. 若finally没有返回值,后面的then都只会成功,且成功的值是finally上一个then的成功数据或者失败原因;

2. 代码实现

js
finally(cb) {
  return this.then(
    data => {
      return Promise.resolve(cb()).then(() => data);
    },
    reason => {js
      Promise.resolve(cb()).then(e => {
        throw e; 
      });
    }
  );
}

Promise.all 手写

1. 要点

  1. promise.all的作用是实现并发请求
  2. promise.all接收一个可迭代对象作为参数,不止数组,数组内每一项可以是promise或普通值,最后返回一个promise,调用then方法可以得到一个结果数组,这个数组内每一项顺序于传入的数组对应。
  3. 只要当数组内有一项失败,则peomise.all最终结果就是失败,全部成功才能成功。

2. 代码实现

js
// Promise.all接受一个可迭代对象作为参数
static all(promises) {
  return new Promise((resolve, reject) => {
    // 判断promises是否为可迭代对象
    if (!isIterable(promises)) {
      throw TypeError(
        `${promises} is not iterable (cannot read property Symbol(Symbol.iterator))`
      );
    }
    let result = []; // 最终结果
    let num = 0;
    //处理数据
    function processData(i, data) {
      result[i] = data;
      // 用计数的方式判断全部都成功
      if (++num === promises.length) {
        resolve(result);
      }
    }

    // 并发请求
    for (let i = 0; i < promises.length; i++) {
      const item = promises[i];
      // 判断item是否为promise
      if (isPromise(item)) {
        item.then((data) => {
          processData(i, data); // 普通值
        }, reject);
      } else {
        // 不是promise
        processData(i, item); // 普通值
      }
    }
  });
}

注意: 不能用result.length === promises.length来判断promise.all全部成功,因为并发请求不一定谁先返回结果,我们通过result[i]赋值,可能得到的是稀疏数组,不准确,例如

js
let arr = []
arr[3] = 1
console.log(arr.length) // 4
console.log(arr) // [empty × 3, 1]
js
// 判断val是否为可迭代对象
function isIterable(val) {
  return val !== null && typeof val[Symbol.iterator] === "function";
}

// 如果一个变量是 Object, 有 then 和 catch 方法, 就认为是 Promise
function isPromise(val) {
  return (
    val !== null &&
    typeof val === "object" &&
    typeof val.then === "function" &&
    typeof val.catch === "function"
  );
}

本节githubCommit记录:Promise.all实现

Promise.allSettled 手写

1. 要点

  1. Promise.allSettled基本功能与all一样, 唯一不同的就是, all只有当所有请求成功后才会成功, allSettled会等待所有请求都有结果后才会返回一个成功的Promise,即使有一个或全部失败,都会返回结果
  2. allSettled返回的promise通过.then调用后, 也得到一个数组, 但是这个数组每一项都会包裹在一个对象里面, 对象中会标识是成功还是失败, 以及成功值或者失败的原因
  3. 若每一项里有promise, 但是这个promisepadding状态,那么Promise.allSettled不会有返回

allSettled返回结果示例

js
Promise.allSettled([...]).then(res=>{ 
 console.log(res) 
})
// 输出
[
  { status: 'rejected', reason: 'xxxx' },
  { status: 'fulfilled', value: 4 }
]

2. 代码实现

js
static allSettled(promises) {
  return new Promise((resolve, reject) => {
    // 判断promises是否是一个可迭代对象
    if (!isIterable(promises)) {
      throw TypeError(
        `${promises} is not iterable (cannot read property Symbol(Symbol.iterator))`
      );
    }
    let result = []; // 结果集合
    let num = 0;

    function processData(i, data, status) {
      if (status === "fulfilled") {
        result[i] = { status, value: data };
      } else {
        result[i] = { status, reason: data };
      }
      if (++num === result.length) {
        resolve(result);
      }
    }

    for (let i = 0; i < promises.length; i++) {
      const item = promises[i];
      if (isPromise(item)) {
        item.then(
          res => {
            processData(i, res, "fulfilled");
          },
          err => {
            processData(i, err, "rejected");
          }
        );
      } else {
        processData(i, item, "fulfilled");
      }
    }
  });
}

本节githubCommit记录:完成allSettled方法

Promise.race 手写

1. 要点

  1. Promise.race是赛跑的意思,接收一个可迭代对象,返回一个promise;
  2. 返回的promise通过then可以拿到里面的成功或者失败

2. 代码实现

js
sstatic race(promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      const item = promises[i];
      // 用resolve包一下,因为有的项可能不是promise
      Promise.resolve(item).then(resolve, reject);
    }
  });
}

用 race 模拟 abort "中断"Promise

1. 要点

  1. promise有一个缺点,就是执行后就不能中断,最多就是不要已经开始执行的promise的结果
  2. ajax上面有一个abort方法,可以终端发出去的请求
  3. 我们可以利用race的特点,封装一个函数,在请求里面加入一个可以自己控制成功或失败的请求,在发出"中断"指令的时候,我们直接让这个请求失败,那么race就会返回我们自己失败的这个请求,从而达到目的.

2. 代码实现

封装

js
function parce(promise) {
  let abort;
  let p = new Promise((resolve, reject) => {
    abort = reject;
  });
  parce.abort = abort;
  return Promise.race([promise, p]);
}

使用

js
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1 ok");
  }, 3000);
});

parce(p1).then(
  res => {
    console.log("成功", res);
  },
  err => {
    console.log("错误", err);
  }
);

setTimeout(() => {
  parce.abort("中断");
}, 1000);

用race实现控制并发请求数量

js
/**
 * @function 并发控制方法
 * @param {Number} poolLimit 同一时间的最大请求数量
 * @param {Array} array 每一次请求的参数组成的数组
 * @param {Function} 真正的请求函数 
 * @returns Promise
 */
 function asyncPool(poolLimit, array, iteratorFn) {
  const ret = []; // 结果集合
  const executing = []; // 请求池

  for (const item of array) {
      const p = Promise.resolve().then(() => iteratorFn(item));
      ret.push(p);

      if (poolLimit <= array.length) {
          const e = p.then(() => executing.splice(executing.indexOf(e), 1));
          executing.push(e);
          if (executing.length >= poolLimit) {
              await Promise.race(executing);
          }
      }
  }
  return Promise.all(ret);
}