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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 重新手写promise,理解核心的异步链式调用原理

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


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之后执行的,这个函数的返回值是由用户决定的。

目录
相关文章
|
6天前
|
存储 前端开发
除了 Promise.all(),还有哪些方法可以处理异步并发操作?
在上述示例中,`concurrentPromises` 函数接受一个Promise数组和最大并发数作为参数,通过手动控制并发执行的Promise数量,实现了对异步操作的并发控制,并在所有Promise完成后返回结果数组。
|
6天前
|
前端开发 JavaScript
如何使用 Promise 处理异步并发操作?
通过使用 `Promise.all()` 和 `Promise.race()` 方法,可以灵活地处理各种异步并发操作,根据不同的业务需求选择合适的方法来提高代码的性能和效率,同时也使异步代码的逻辑更加清晰和易于维护。
|
3天前
|
前端开发 数据处理
如何使用 Promise.all() 处理异步并发操作?
使用 `Promise.all()` 可以方便地处理多个异步并发操作,提高代码的执行效率和可读性,同时通过统一的 `.catch()` 方法能够有效地处理异步操作中的错误,确保程序的稳定性。
|
7天前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
6天前
|
前端开发 JavaScript 开发者
用 Promise 处理异步操作的优势是什么?
综上所述,使用Promise处理异步操作能够有效地解决传统回调函数带来的诸多问题,提高代码的质量、可读性、可维护性和可扩展性,是JavaScript中进行异步编程的重要工具和技术。
|
2月前
|
前端开发 JavaScript
解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
该文章教授了如何使用Promise和async/await来解决异步编程问题,从而避免回调地狱,使代码更加清晰和易于管理。
解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
|
4月前
|
前端开发 JavaScript
Vue 中 Promise 的then方法异步使用及async/await 异步使用总结
Vue 中 Promise 的then方法异步使用及async/await 异步使用总结
135 1
|
3月前
|
存储 JSON 前端开发
JavaScript异步编程3——Promise的链式使用
JavaScript异步编程3——Promise的链式使用
30 0
|
3月前
|
前端开发 JavaScript
JavaScript——promise 是解决异步问题的方法嘛
JavaScript——promise 是解决异步问题的方法嘛
45 0
|
3月前
|
前端开发 JavaScript
ES6新特性(五):Promise优雅地处理异步
ES6新特性(五):Promise优雅地处理异步

热门文章

最新文章