重新手写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
本身是个函数,这个函数的参数是另外一个函数。promise
是then
这个函数返回的,而和另外一个函数无关!
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
之后执行的,这个函数的返回值是由用户决定的。