重新手写promise,理解核心的异步链式调用原理

986 阅读5分钟

promise的手写版,平时业务几乎用不到,但手写会对加深对promise的理解,本文侧重理解核心的异步链式调用原理。

这边在看了最简实现 Promise,支持异步链式调用(20 行),试着再重新手写一次。

同样,这个 Promise 的实现不考虑任何异常情况,只考虑代码最简短,从而便于理解核心的异步链式调用原理。

executor 立即执行

promise 的本质是一个对象,由构造函数Promise产生实例。

产生实例的时候,需要传个executor函数,此函数直接执行。

先看看最简单的 promise,可以丢在控制台:

new Promise(() => {
  // 这句立即执行
  console.log(2);
});

仅仅实现这种程度的话,其实超简单:

function Promise(executor) {
  executor();
}

resolve 延时执行

但一般不会这么用的,executor至少也得有个resolve

resolve之后,会执行这个promise的所有then函数,而then这个函数,参数也是一个函数,这个函数将拿到resolve的值。

var p1 = new Promise((resolve) => {
  resolve(2);
});
p1.then((value) => {
  console.log('then1', value); // 2
});
p1.then((value) => {
  // 2
  console.log('then2', value); // 2
});

这里有个典型的发布-订阅模式,then 其实是订阅resolve发布。 这种模式的通病:用数组装函数,订阅的本质就是将函数放进数组,发布的本质就是数组的函数挨个执行。

then每个实例上面都有,所以是定义在原型链上面的。

于是:

function Promise(executor) {
  // 数组收集函数
  this.cbs = [];
  // resolve执行的时候,就是数组里的函数挨个执行
  const resolve = (value) => {
    // 但是这里是空的
    console.log(this.cbs);
    this.cbs.forEach((fn) => fn(value));
  };
  executor(resolve);
}
Promise.prototype.then = function (onFulfill) {
  // 订阅就是数组里增加函数
  this.cbs.push(onFulfill);
};

但是这样是不行滴,书写顺序上resolve先执行,then后执行,所以resolve 执行的时候,cbs 是空的。

怎么样让then先执行,之后再让resolve执行呢?

书写的先后顺序肯定不能改了,那想想别的法子让执行顺序发生变化,最容易想到的就是resolve延时执行。

const resolve = (value) => {
  setTimeout(() => {
    this.cbs.forEach((fn) => fn(value));
  });
};

再运行,就没有问题了!

then 的链式调用

then函数,其实可以链式调用的。

var p1 = new Promise((resolve) => {
  resolve(2);
});
var p2 = p1.then((value) => {
  console.log('then2', value);
});
// 注意这里换成了p2,链式调用的体现
p2.then(() => {
  console.log('链式调用的then');
});

p2.then(() => {
  console.log('链式调用的then2');
});

所以then实际上返回的是一个新的promise

因为then是一个新的promise,所以要想后面的then能执行,内部一定会有resolve

这里特别注意!then本身是个函数,这个函数的参数是另外一个函数。promisethen这个函数返回的,而和另外一个函数无关!


Promise.prototype.then = function (onFulfill) {
  this.cbs.push(onFulfill);
  // then肯定返回promise
  return new Promise((resolve) => {
    resolve();
  });
};

难点:then 的链式调用 拿到 前一个then的参数函数返回值

then函数,除了链式调用,还可以拿到前个then的参数函数返回值,注意是参数函数的返回值,而不是 then 本身的返回值,then本身肯定是返回promise的。

var p1 = new Promise((resolve) => {
  resolve(2);
});
var p2 = p1.then((value) => {
  console.log('then2', value);
  return 3;
});
// 注意这里换成了p2,链式调用的体现
p2.then((value) => {
  console.log('链式调用的then', value); // 3
});

p2.then((value) => {
  console.log('链式调用的then2', value); // 3
});
// p1有1个回调函数
console.log(p1)
// p2有2个回调函数
console.log(p2)

then能执行的前提是,对应的promise执行了resolve,这样能拿到resolve的值。

所以后面的 then 能拿到的前提是:前面的 then(返回值是promise) 将参数函数返回值resolve了。这里面略绕,得好好想想。

综上实现下:


Promise.prototype.then = function (onFulfill) {
  // then肯定返回promise
  return new Promise((resolve) => {
    // 这里面略绕,resolve需要函数的返回值,但是函数只能在cbs里执行,所以加工了onFulfill
    const fn = (value) => {
      const returnValue = onFulfill(value);
      // 为了让后面的then能执行,这里添加resolve
      resolve(returnValue);
    };
    // 这里注意!push的fn是加工后的onFulfill,fn执行之后,resolve才能执行
    this.cbs.push(fn);
  });
};

then 的参数函数返回 promise

then 的参数函数,如果返回 promise(userPromise) 的话,必须解析完 promise 之后的结果再传递到下一个 then

var p1 = new Promise((resolve) => {
  resolve(2);
});
var p2 = p1.then((value) => {
  console.log('then2', value);
  return new Promise((resolve) => resolve(3));
});
// 注意这里换成了p2,链式调用的体现
p2.then((value) => {
  console.log('链式调用的then', value); // 3
});

p2.then((value) => {
  console.log('链式调用的then2', value); // 3
});

想拿到 promise 的解析结果,其实直接在后面调用 then 即可。

// 稍微判断下返回值
const fn = (value) => {
  const returnValue = onFulfill(value);
  // 返回值是不是promise
  if (returnValue instanceof Promise) {
    const userPromise = returnValue;
    // 是的话,将解析完之后的结果resolve
    userPromise.then(resolve);
  } else {
    resolve(returnValue);
  }
};

终版的最核心的 promise 就是

function Promise(executor) {
  // 数组收集函数
  this.cbs = [];
  // resolve执行的时候,就是数组里的函数挨个执行
  const resolve = (value) => {
    setTimeout(() => {
      this.cbs.forEach((fn) => fn(value));
    });
  };
  executor(resolve);
}
Promise.prototype.then = function (onFulfill) {
  // then肯定返回promise
  return new Promise((resolve) => {
    // 这里面略绕,resolve需要函数的返回值,但是函数只能在cbs里执行,所以加工了onFulfill
    const fn = (value) => {
      const returnValue = onFulfill(value);
      // 为了让后面的then能执行,这里添加resolve
      if (returnValue instanceof Promise) {
        const userPromise = returnValue;
        userPromise.then(resolve);
      } else {
        resolve(returnValue);
      }
    };
    // 这里注意!push的fn是加工后的onFulfill
    this.cbs.push(fn);
  });
};

总结

当然promise本身有其状态和值,还有很多边界情况,暂时没做考虑,后期再慢慢研究吧。

resolve执行其实就是then的参数函数执行。

同样,想知道promise的解析结果,也在then里才能拿到。

then本身是个函数,挂载在原型链上,里面的this是当前的promise实例,且返回新的promise实例 而其参数是个函数,这个函数是在resolve之后执行的,这个函数的返回值是由用户决定的。