手写Promise,超级详细!!!

206 阅读8分钟

1. 在Promise的构造函数中我们需要做哪些呢

// 1)校验executor,需要是函数
// 2)初始化promise状态,status, value, reason
// 3)创建存放状态改变后回调的List。onResolvedCallbacks, onResolvedCallbacks
// 4)创建一个resolve和reject两个回调函数提供给executor,用户用来改变promise状态
// 5)执行executor
function Promise(executor) {
  if (typeof executor !== "function") {
    throw new Error("Promise构造函数的参数类型为函数");
  }
  const that = this;
  that.status = PENDING;
  that.value = undefined;
  that.reason = undefined;
  that.onResolvedCallbacks = [];
  that.onResolvedCallbacks = [];
  const resolve = function (value) {
    // resolve参数处理,如果参数是Promise实例则求出promise结果
    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }
    // 只有PENDING状态才能执行(promise初始化后没有改变过状态)
    if (that.status === PENDING) {
      that.status = FULFILLED;
      that.value = value;
      // 这里可以理解为发布订阅模式,这里相当于Promise实例改变后开始调用对应的监听函数
      that.onResolvedCallbacks.forEach((cb) => cb());
    }
  };
  const reject = function (reason) {
    if (that.status === PENDING) {
      that.status = REJECTED;
      that.reason = reason;
      // 这里可以理解为发布订阅模式,这里相当于Promise实例改变后开始调用对应的监听函数
      that.onRejectedCallbacks.forEach((cb) => cb());
    }
  };
  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

2.在原型上添加then方法,接受Promise的执行结果

 //我们一般是这样来调用promise的。
 const promise1 = new Promise((resolve, reject) => {})
 // 将用户自己new的称为promise1
 promise1.then(
     (value) => {},
     (reason) => {}
 )
 Promise.then = function(){
     ....
     // then里面会生成一个新的promise,我们称为promise2
     // then内部还会调用一个处理promise2的状态和返回值的函数,我们称为resolvePromise
     resolvePromise(promise2, x, resolve, reject);
     /* 
         promise2: 
         x: promise1.then()传入的成功回调 
         resolve: promise2的resolve 
         reject: promise2的reject 
      */
 }
// 1)then接收两个参数,Promise实例(用户自己new的,promise1)的成功回调和失败回调
// 2)then内部生成一个新的Promise实例(promise2)作为返回值,以便链式调用
// 3)状态为PENDING则将回调函数添加到对应的List(onResolvedCallbacks, onResolvedCallbacks)
// 4)状态不为PENDING则拿到value或者reason执行。中间需要对用户传输的回调做try/catch处理。有问题则直接reject,交给下一个then或者终结
// 5)将两个回调的返回值传递到下一个then
Promise.prototype.then = function (onFulfilled, onRejected) {
  const that = this;
  //参数处理,这里也是then方法会发生值参透的原因,直接将promise结果作为本次then的返回值
  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : (r) => {
          throw r;
        };
  // Promise的then支持链式调用,又由于Promise实例只能改变一次状态,这里我们需要返回一个新的Promise实例
  // 由于Promise参数方法(executor)是立即执行的,我们将上一个Promise实例(promise1)的状态改变的回调放到新的Promise实例的excute里面执行
  const promise2 = new Promise((resolve, reject) => {
    if (that.status === PENDING) {
      // 这里可以理解为发布订阅模式,相当于开始订阅Promise实例状态的改变
      that.onResolvedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onFulfilled(that.value);
            // 我们来处理一下成功回调返回值与新生成的Promise实例的关系。
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            // 处理异常
            // 注意: 这里改变的是 then()内部新生成的实例
            // 当我们let p1 = xxx.then(...); p1.then((x)=> {},(y)=>{}])
            // x,y是p1的value和reason,并非是xxx的value和reason
            // 所以这里我们调用的是新实例的reject
            reject(error);
          }
        });
      });
      that.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            // 同成功回调的处理
            reject(error);
          }
        });
      });
    }
    if (that.status === FULFILLED) {
      // PromiseA+规范中要求onFulfilled, onRejected这两个状态处理函数是异步执行。至于是宏任务还是微任务并不关心
      // 我们平时使用原生Promise时,不论promise实例状态如何,都会异步执行
      // 原生Promise使用的是微任务,这里我们使用setTimeout代替。
      setTimeout(() => {
        try {
          // 将参数传递进成功的处理函数
          // 注意: 这里的that是调用then的Promise实例(promise1),而非then()内部生成的新的Promise实例(promise2)
          let x = onFulfilled(that.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          // 如果成功处理函数执行异常,则将当前Promise状态改变为 REJECTED
          // 注意: 这里改变的是 then()内部新生成的实例(promise2)
          reject(error);
        }
      });
    }
    if (that.status === REJECTED) {
      // PromiseA+规范中要求onFulfilled, onRejected这两个状态处理函数是异步执行。至于是宏任务还是微任务并不关心
      // 我们平时使用原生Promise时,不论promise实力状态如何,都会异步执行
      // 原生Promise使用的是微任务,这里我们使用setTimeout代替。
      setTimeout(() => {
        try {
          // 将参数传递进失败的处理函数
          // 注意: 这里的that是调用then的Promise实例,而非then()内部生成的新的Promise实例
          let x = onRejected(that.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          // 如果失败处理函数执行异常,则将当前Promise状态改变为 REJECTED
          // 注意: 这里改变的是 then()内部新生成的实例
          reject(error);
        }
      });
    }
  });
  return promise2;
};

3. resolvePromise,处理then返回的promise状态

// 用于处理then内部Promise实例状态的方法。
const resolvePromise = function (promise2, x, resolve, reject) {
  //解析promise2 和 x 之间的关系 来判断promise2走resolve还是reject
  if (promise2 === x) {
    // 如果用户传入的成功回调函数的返回值是promise2,则报错
    return reject(new TypeError("Chaining cycle detected for promise..."));
  }

  // if(x instanceof Promise){
  //    此处兼容原生promise
  // }
  // 如果用户x的返回值是Promise实例,这需要得到该Promise实例的运行结果再返回(即状态x不再为PENDING)
  // 依据PromiseA+规范,此处认为对象具有then方法即为promise实例。
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    //这里声明called为flag,为了避免其他人的promise状态改变多次(既调用resolve,又调用reject)
    let called = false;
    try {
      let then = x.then;
      // let then = x.then与x.then本质上没有区别。但如果then是使用Object.defineProperty定义的,每次获取的then肯能值不一样
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            // 用户可能在then方法的成功回调中多次嵌套使用Promise
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 如果以上都不是,则直接成功返回
    resolve(x);
  }
};

4. 以上Promise完成。all, race等方法部署与PromiseA+规范内容,我们后面介绍,先进行测试。

// 官方有对应的工具包可以使用。
// npm install promises-aplus-tests -g
// 我们在刚才代码的基础加上:
Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};
module.exports = Promise;
// 终端执行 promises-aplus-tests promise.js

5. Promise.resolve, Promise.reject

Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    resolve(value);
  });
};

Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  });
};

6. catch, finally实现

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};

// 调用promise的then拿到结果,
// 使用Promise.resolve调用finall,包装成成功状态Promise。
// 再调用then,将promise的结果传递下去。这里注意返回的是调用finally的promise的value或者reason
Promise.prototype.finally = function (finall) {
  return this.then(
    (value) => {
      return Promise.resolve(finall()).then(() => value);
    },
    (reason) => {
      return Promise.resolve(finall()).then(() => {
        throw reason;
      });
    }
  );
};

7. Promise.all, Promise.race, Promise.settled

Promise.all = function (values) {
  return new Promise((resolve, reject) => {
    let result = [];
    let times = 0;
    function processMap(index, data) {
      result[index] = data;
      if (++times === values.length) {
        resolve(result);
      }
    }
    for (let i = 0; i < values.length; i++) {
      let current = values[i];
      Promise.resolve(current).then((data) => {
        processMap(i, data);
      }, reject);
    }
  });
};

Promise.race = function (values) {
  return new Promise((resolve, reject) => {
    values.forEach((item) => {
      Promise.resolve(item).then(resolve, reject);
    });
  });
};

Promise.allSettled = function (values) {
  return Promise((resolve, reject) => {
    let times = 0;
    let result = [];
    function processMap(key, value) {
      result[key] = value;
      if (++times === values.length) {
        resolve(result);
      }
    }
    for (let index = 0; index < values.length; index++) {
      const current = values[index];
      Promise.resolve(current)
        .then((value) => {
          processMap(index, { status: "fulfilled", value });
        })
        .catch((reason) => {
          processMap(index, { status: "rejected", reason });
        });
    }
  });
};

8. Promise做超时处理

function withAbort(userPromise) {
  let abort;
  let innerPromise = new Promise((resolve, reject) => (abort = reject));
  let p = Promise.race([innerPromise, userPromise]);
  p.abort = abort;
  return p;
}
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("ok");
  }, 2000);
});
p = withAbort(p);
p.then((data) => {
  console.log(data, "成功");
}).catch((reason) => {
  console.log(reason);
});
setTimeout(() => {
  p.abort("超时了");
}, 1000);

9. 将函数promise化

// node.js中经常因为读取文件而产生回调地狱
// 我们可以使用Promise封装一下(原生已经支持promise版本的api)
function promisify(fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      fn(...args, function (err, data) {
        if (err) return reject(err);
        resolve(data);
      });
    });
  };
}

function promisifyAll(obj) {
  for (let key in obj) {
    if (typeof obj[key] == "function") {
      obj[key] = promisify(obj[key]);
    }
  }
}
promisifyAll(fs)

10. calss形式的Promise

const PENDING = "PENGDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

const resolvePromise = function (promise2, x, resolve, reject) {
  //解析promise2 和 x 之间的关系 来判断promise2走resolve还是reject
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise..."));
  }

  // if(x instanceof Promise){
  //    此处兼容原生promise
  // }
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    let called = false;
    // 依据PromiseA+规范,此处认为对象具有then方法即为promise实例。
    // 别人写的promise可能既调用了reject又调用了reject(多次改变promise状态)
    // 那这个flag控制promise的状态只能改变一次
    try {
      let then = x.then;
      if (typeof then === "function") {
        // x.then(()=>{},()=>{});
        // 与x.then本质上没有区别。但如果then是使用Object.defineProperty定义的,每次获取的then肯能值不一样
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
};

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach((cb) => cb());
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((cb) => cb());
      }
    };
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (r) => {
            throw r;
          };
    const p = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(p, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(p, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(p, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(p, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
      }
    });
    return p;
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  finally(finall) {
    return this.then(
      (value) => {
        return Promise.resolve(finall()).then(() => value);
      },
      (reason) => {
        return Promise.resolve(finall()).then(() => {
          throw reason;
        });
      }
    );
  }
}

Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    resolve(value);
  });
};

Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  });
};

Promise.all = function (values) {
  return new Promise((resolve, reject) => {
    let result = [];
    let times = 0;
    function processMap(index, data) {
      result[index] = data;
      if (++times === values.length) {
        resolve(result);
      }
    }
    for (let i = 0; i < values.length; i++) {
      let current = values[i];
      Promise.resolve(current).then((data) => {
        processMap(i, data);
      }, reject);
    }
  });
};


Promise.race = function (values) {
  return new Promise((resolve, reject) => {
    values.forEach((item) => {
      Promise.resolve(item).then(resolve, reject);
    });
  });
};

Promise.allSettled = function (values) {
  return Promise((resolve, reject) => {
    let times = 0;
    let result = [];
    function processMap(key, value) {
      result[key] = value;
      if (++times === values.length) {
        resolve(result);
      }
    }
    for (let index = 0; index < values.length; index++) {
      const current = values[index];
      Promise.resolve(current)
        .then((value) => {
          processMap(index, { status: "fulfilled", value });
        })
        .catch((reason) => {
          processMap(index, { status: "rejected", reason });
        });
    }
  });
};

Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

module.exports = Promise;