手写一个Promise

实现Promise之前,需要先知道Promise有哪些能力。从最基础的功能开始,逐渐扩展出完整的功能。

浏览器中的Promise

首先,在浏览器中看一下原生的Promise是怎样的。

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hello worlld');
  }, 1000);
});

console.log(p);
p.then((result) => {
  console.log(result);
});

将上述代码复制到浏览器控制台运行,打印出的p实例如下。

可以看到其包含catch方法,then方法,finally方法,并且还包括一些内部属性,[[PromiseState]]表示当前 promise 的状态,[[PromiseResult]]表示 promise 成功后的结果。

在间隔一秒之后,控制台会打印出hello world

在这里,通过setTimeout控制在 1 秒之后调用resolve函数,Promiseresolve调用的时候进入then方法,并且resolve的参数作为then发放的参数。

初始化内部结构

首先来创建一个基本的Promise类。

// 此处的callback参数模拟Promise中接收的回调参数
// callback回调具有两个参数,第一个参数为`resolve`,表示成功,第二个参数为`reject`,表示失败。
var MyPromise = function MyPromise(callback) {
  // 通过promiseState模拟[[PromiseState]]
  this.promiseState = 'pending';
  // 通过promiseResult模拟[[PromiseResult]]
  this.promiseResult = undefined;

  const resolve = (resolveValue) => {};

  const reject = (rejectedValue) => {};

  callback(resolve, reject);

  // then方法
  MyPromise.prototype.then = function (callback) {};

  // catch方法
  MyPromise.prototype.catch = function (callback) {};
};

首先初始化我们MyPromise类的基本属性和方法,由于我们不能做到使用[[PromiseState]][[PromiseResult]]这种形式的内部属性,所以就用类内部的普通属性promiseStatepromiseResult来模拟它们的作用。当然,还有两个必要的方法,thencatch

保持正确的 Promise 状态

我们知道,Promise 具有三种状态,分别是fulfilledrejectedpending。分别表示成功、失败和进行中,默认情况下状态为pending,到调用resolve的时候,状态变更为fulfilled,并且将resolve中的值传递给promiseResult,调用reject时,变更为rejected

那么,resolvereject添加一些状态变更如下。

const resolve = (resolveValue) => {
  this.promiseState = 'fulfilled';
  this.promiseResult = resolveValue;
};

const reject = (rejectedValue) => {
  this.promiseState = 'rejected';
};

还有一点,在Promise中,状态变更只能发生一次,比如状态已经变为fulfilled,那么该Promise的状态不可以再变更为pending或者rejected。所以,我们还需要在变更之前做一个检测。

const resolve = (resolveValue) => {
  if (this.promiseState === 'pending') {
    this.promiseState = 'fulfilled';
    this.promiseResult = resolveValue;
  }
};

const reject = (rejectedValue) => {
  if (this.promiseState === 'pending') {
    this.promiseState = 'rejected';
  }
};

MyPromise异步执行

现在,给我们已经实现好的部分添加一些日志,执行看运行结果是什么样子。

var MyPromise = function MyPromise(callback) {
  this.promiseState = 'pending';
  this.promiseResult = undefined;

  const resolve = (resolveValue) => {
    if (this.promiseState === 'pending') {
      this.promiseState = 'fulfilled';
      this.promiseResult = resolveValue;
    }
  };

  const reject = (rejectedValue) => {
    if (this.promiseState === 'pending') {
      this.promiseState = 'rejected';
    }
  };

  callback(resolve, reject);

  MyPromise.prototype.then = function (callback) {
    console.log('执行promise then方法');
  };

  MyPromise.prototype.catch = function (callback) {
    console.log('执行promise catch方法');
  };
};

console.log('在创建promise之前');
var p = new MyPromise((resolve, reject) => {
  console.log('创建promise');
  setTimeout(() => {
    resolve('hello world');
  }, 1000);
});

p.then((res) => {
  console.log('执行then回调', res);
});
console.log('在创建promise之后');

浏览器控制台输出如下。

在创建promise之前
创建promise
执行promise then方法
在创建promise之后

出现问题了,这里是同步执行的输出,如果是的正常的Promise,应该是异步的结果,而且这里then即使没有在MyPromiseresolve,它也会执行。所以需要解决两个问题,首先控制MyPromise异步执行,可以使用setTimeout来模拟,然后是then方法的执行时机控制。

// 此处的callback参数模拟Promise中接收的回调参数
// callback回调具有两个参数,第一个参数为`resolve`,表示成功,第二个参数为`reject`,表示失败。
var MyPromise = function MyPromise(callback) {
  // 通过promiseState模拟[[PromiseState]]
  this.promiseState = 'pending';
  // 通过promiseResult模拟[[PromiseResult]]
  this.promiseResult = undefined;
  // then的回调函数
  this._thenCallback = undefined;

  const resolve = (resolveValue) => {
    if (this.promiseState === 'pending') {
      this.promiseState = 'fulfilled';
      this.promiseResult = resolveValue;
      // 在这里,使用setTimeout来模拟异步代码执行
      // 在异步回调里,首先检测then回调是不是存在,如果存在就执行一下
      // 明明这里不是先运行的,this._thenCallback后注册的吗,怎么这里就直接检测执行了?
      // 这里利用了setTimeout宏任务,宏任务在任务队列的末尾,所以执行到setTimeout中的时候,this._thenCallback已经注册完毕了
      setTimeout(() => {
        if (this._thenCallback) {
          // 将要resolve的值传递给then回调的第一个参数
          this._thenCallback(resolveValue);
        }
      });
    }
  };

  const reject = (rejectedValue) => {
    if (this.promiseState === 'pending') {
      this.promiseState = 'rejected';
    }
  };

  callback(resolve, reject);

  // then方法
  MyPromise.prototype.then = function (callback) {
    console.log('执行promise then方法');
    // 返回一个新的该类的实例,保证then能够继续链式调用
    return new MyPromise((resolve, reject) => {
      // 在这里注册then的回调,value为回调的第一个参数,即resolve的值
      this._thenCallback = (value) => {
        // 直接执行then中的回调函数,将其返回值resolve到新的promise中,让其能继续链式调用,并且后续能拿到此值
        const result = callback(value);
        resolve(result);
      };
    });
  };

  // catch方法
  MyPromise.prototype.catch = function (callback) {};
};

再次运行之前的结果,输出

在创建promise之前
创建promise
执行promise then方法
在创建promise之后
执行then回调 hello world

then能返回一个MyPromise对象

我们知道,Promisethen中可以继续返回一个Promise,在下一个then中获取到的就是其resolve的值。我们再对上面的then方法优化一下,让他支持自动执行promise

this._thenCallback = (value) => {
  // 直接执行then中的回调函数,将其返回值resolve到新的promise中,让其能继续链式调用,并且后续能拿到此值
  const result = callback(value);
  // 由于可以在then中继续返回一个promise,在这里需要判断一下结果的类型
  if (result instanceof MyPromise) {
    // 是一个MyPromise实例的话,就在它的then中拿到结果,resolve出去
    result.then((res) => {
      resolve(res);
    });
  } else {
    // 非MyPromise情况
    resolve(result);
  }
};

// 省略部分代码

var p = new MyPromise((resolve, reject) => {
  console.log('创建promise');
  setTimeout(() => {
    resolve('hello world');
  }, 1000);
});

p.then((res) => {
  console.log('执行then回调', res);
  return res;
})
  .then((res) => {
    return new MyPromise((resolve) => {
      resolve(res + ' xxxx');
    });
  })
  .then((res) => {
    console.log(res);
  });

执行后控制台输出

执行promise then方法
执行promise then方法
执行then回调 hello world
执行promise then方法
hello world xxxx

至此,then方法基本上实现的差不多了。catch方法和then方法类似,不再赘述。

MyPromise.resolveMyPromise.reject

MyPromise.resolveMyPromise.reject的实现比较简单,直接放上代码。

MyPromise.resolve = function (value) {
  return new MyPromise((resolve) => {
    resolve(value);
  });
};

MyPromise.reject = function (error) {
  return new MyPromise((resolve, reject) => {
    reject(error);
  });
};

调用这两个方法可以快捷的resolvereject

但是真的结束了吗?运行如下示例。

var p = new MyPromise((resolve, reject) => {
  console.log('创建promise');
  setTimeout(() => {
    reject('some error');
  }, 1000);
});

p.then((res) => {
  console.log('执行then回调', res);
  return res;
})
  .then((res) => {
    return MyPromise.resolve(res + 'xxx');
  })
  .then((res) => {
    console.log(res);
  })
  .catch((e) => {
    console.log(e);
  });

输出结果

创建promise
执行promise then方法
执行promise then方法
执行promise then方法
执行then回调 some error

从上面可以看到执行了很多个then回调,但是在原生的Promise中,catch之前的then应该都要跳过执行,只执行catch回调,这里需要对then再完善一下。

this._thenCallback = (value) => {
  // 在reject之后直接跳过then中相关逻辑的执行
  if (this.promiseState !== 'rejected') {
    // 直接执行then中的回调函数,将其返回值resolve到新的promise中,让其能继续链式调用,并且后续能拿到此值
    const result = callback(value);
    // 由于可以在then中继续返回一个promise,在这里需要判断一下结果的类型
    if (result instanceof MyPromise) {
      // 如果这个promise已经是rejected状态,那么直接进入它的catch回调执行,将结果再次reject出去
      if (result.promiseState === 'rejected') {
        result.catch((error) => {
          reject(error);
        });
      } else {
        // 是一个MyPromise实例的话,就在它的then中拿到结果,resolve出去
        result.then((res) => {
          resolve(res);
        });
      }
    } else {
      // 非MyPromise情况
      resolve(result);
    }
  } else {
    // 跳过then,直接向外reject
    reject(value);
  }
};

到这里,一个简单的模拟Promise实现基本完成了,最后上一下完整代码。

// 此处的callback参数模拟Promise中接收的回调参数
// callback回调具有两个参数,第一个参数为`resolve`,表示成功,第二个参数为`reject`,表示失败。
var MyPromise = function MyPromise(callback) {
  // 通过promiseState模拟[[PromiseState]]
  this.promiseState = 'pending';
  // 通过promiseResult模拟[[PromiseResult]]
  this.promiseResult = undefined;
  // then的回调函数
  this._thenCallback = undefined;
  // catch的回调函数
  this._catchCallback = undefined;

  const resolve = (resolveValue) => {
    if (this.promiseState === 'pending') {
      this.promiseState = 'fulfilled';
      this.promiseResult = resolveValue;
      // 在这里,使用setTimeout来模拟异步代码执行
      // 在异步回调里,首先检测then回调是不是存在,如果存在就执行一下
      // 明明这里不是先运行的,this._thenCallback后注册的吗,怎么这里就直接检测执行了?
      // 这里利用了setTimeout宏任务,宏任务在任务队列的末尾,所以执行到setTimeout中的时候,this._thenCallback已经注册完毕了
      setTimeout(() => {
        if (this._thenCallback) {
          // 将要resolve的值传递给then回调的第一个参数
          this._thenCallback(resolveValue);
        }
      });
    }
  };

  const reject = (rejectedValue) => {
    if (this.promiseState === 'pending') {
      this.promiseState = 'rejected';
      setTimeout(() => {
        if (this._catchCallback) {
          this._catchCallback(rejectedValue);
        } else if (this._thenCallback) {
          // reject之后还可以继续调用.then
          // 所以此处检测它是不是有then回调,如果有的话,就调用then回调
          this._thenCallback(rejectedValue);
        } else {
          throw new Error('缺少catch');
        }
      });
    }
  };

  callback(resolve, reject);

  // then方法
  MyPromise.prototype.then = function (callback) {
    console.log('执行promise then方法');
    // 返回一个新的该类的实例,保证then能够继续链式调用
    return new MyPromise((resolve, reject) => {
      // 在这里注册then的回调,value为回调的第一个参数,即resolve的值
      this._thenCallback = (value) => {
        // 在reject之后直接跳过then中相关逻辑的执行
        if (this.promiseState !== 'rejected') {
          // 直接执行then中的回调函数,将其返回值resolve到新的promise中,让其能继续链式调用,并且后续能拿到此值
          const result = callback(value);
          // 由于可以在then中继续返回一个promise,在这里需要判断一下结果的类型
          if (result instanceof MyPromise) {
            // 如果这个promise已经是rejected状态,那么直接进入它的catch回调执行,将结果再次reject出去
            if (result.promiseState === 'rejected') {
              result.catch((error) => {
                reject(error);
              });
            } else {
              // 是一个MyPromise实例的话,就在它的then中拿到结果,resolve出去
              result.then((res) => {
                resolve(res);
              });
            }
          } else {
            // 非MyPromise情况
            resolve(result);
          }
        } else {
          // 跳过then,直接向外reject
          reject(value);
        }
      };
    });
  };

  // catch方法
  MyPromise.prototype.catch = function (callback) {
    return new MyPromise((resolve, reject) => {
      this._catchCallback = (value) => {
        const result = callback(value);
        if (result instanceof MyPromise) {
          result.then((res) => {
            resolve(res);
          });
        } else {
          resolve(result);
        }
      };
    })
  };
};

MyPromise.resolve = function (value) {
  return new MyPromise((resolve) => {
    resolve(value);
  });
};

MyPromise.reject = function (error) {
  return new MyPromise((resolve, reject) => {
    reject(error);
  });
};

总结

Promise是开发中用的十分频繁的一个功能了,他解决回调地狱的问题,让代码结构更加清晰,了解它的特性和实现原理可以帮助我们更好的使用Promise

本文完。😊

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的电子邮箱地址不会被公开。