Promise原理 渐进渐知

一、极简promise雏形

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;
    };
    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }
    fn(resolve);
}

下面应用一下这个手写的Promise基本原理:

let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5秒');
    }, 5000);
}).then((tip) => {
    console.log(tip);
})

运行结果如下:

约5秒钟后出现“done 5秒钟”

image

下面运用调试功能,看一下基本的运行逻辑:

let p = new Promise......

这一行上面打断点,调试后代码运行在此行上,点“步入”,进入构造函数

function Promise(fn)

image

callbacks数组为“undefined”

image

从第5步直接跳转至第6步,如下:

image

第7步之后跳到第8步,完成p实例化;第9步开始调用.then函数

image

从第11步可以看出,

(tip) => {
    console.log(tip);

已经压栈进入callbacks数组中。

image

第12步完成.then函数的调用,第13步进入setTimeout异步函数的调用;

image

第14步,输出了“done”

image

第15步,进入resolve函数

image

第16步,value值为“5秒"

image

第17步,相当于第16步时,callback(value)调用的是:

(value) => {
    console.log(value);

image

第18步,

(tip) => {
    console.log(tip);
}

函数调试结束

image

第19步,forEach函数调用结束 ;

image

第20步,resolve函数调用结束 ;

image

第21步,setTimout函数调用结束 ;

至此,调试完毕!

image

综合上述:

上述代码很简单,大致的逻辑是这样的:

  1. 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
  2. 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;

首先 new Promise 时,传给 Promise 的函数设置定时器模拟异步的场景,因为setTimeout为异步,没有立即执行,而是紧接着调用 Promise 对象的 then 方法;调用then方法注册异步操作完成后的 onFulfilled(这一步也就相当于将匿名函数压入callbacks数组栈中);然后,又开始执行setTimeout异步函数,在异步函数setTimeout中,调用 resolve(value), 执行 then 方法注册的 onFulfilled。

then 方法注册的 onFulfilled 是存在一个数组中,可见 then 方法可以调用多次,注册的多个onFulfilled 会在异步操作完成后根据添加的顺序依次执行。如下:

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        // return this;
    };

    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}

let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5秒');
    }, 5000);
})

p.then((tip) => {
    console.log("then1",tip);
})
p.then((tip) => {
    console.log("then2",tip);
})

运行结果如下:

image

上例中,要先定义一个变量 p ,然后 p.then 两次。而规范中要求,then 方法应该能够链式调用。实现也简单,只需要在 then 中添加 return this 即可。如下所示:

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;    //this 指向调用这个方法的对象实例;
    };

    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}

let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5秒');
    }, 5000);
}).then((tip) => {
    console.log("then1",tip);
}).then((tip) => {
    console.log("then2",tip);
})

运行结果如下:

image

二、加入延时机制

上面 Promise 的实现存在一个问题:如果在 then 方法注册 onFulfilled 之前,resolve 就执行了,onFulfilled 就不会执行到了。比如上面的例子中我们把 setTimout 去掉:

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;    //this 指向调用这个方法的对象实例;
    };

    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}

let p = new Promise(resolve =>{
        console.log('同步执行1');
        resolve('同步执行2');
}).then((tip) => {
    console.log("then1",tip);
}).then((tip) => {
    console.log("then2",tip);
})

运行结果如下

image

执行结果显示,只有 "同步执行1" 被打印了出来,后面的”同步执行2“ 、"then1" 和 "then2" 均没有打印出来。再回去看下 Promise 的源码,也很好理解,在调用resolve函数 执行时,callbacks 还是空数组,还没有onFulfilled 注册上来,所以resolve、then1、then2都没有执行。

在前面的调试中已经看到,是先执行.then函数将其中的匿名函数注册到callbacks数组中,再调用的setTimeout函数中的resolve,因为callbacks数组中已经压入了匿名函数,所以resolve函数可以调用并执行。纵观整个Promise,执行顺序为then->setTimeout->resolve,这样就保证了Promise的顺利执行。

Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此要加入一些处理,保证在 resolve 执行之前,then 方法已经注册完所有的回调:

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;    //this 指向调用这个方法的对象实例;
    };

    //改写resolve函数,增加setTimeout;
    function resolve(value) {
    setTimeout(function() {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 0)}

    fn(resolve);
}

let p = new Promise(resolve =>{
        console.log('同步执行1');
        resolve('同步执行2');
}).then((tip) => {
    console.log("then1",tip);
}).then((tip) => {
    console.log("then2",tip);
})

运行结果如下

image

上述代码中,我们在 resolve 中增加定时器,通过 setTimeout 机制,将 resolve 中执行回调的逻辑放置到JS任务队列末尾,以保证先执行then方法的 onFulfilled注册完成,再执行resolve函数(注:这时callbacks数组中已经压入then1和then2两个匿名函数,resolve函数就可以调用并使用了)。

但是这样依然存在问题,在 resolve 执行后,再通过 then 注册上来的 onFulfilled 都没有机会执行了。如下所示,我们加了延迟后,then1 和 then2 可以打印出来了,但下例中的 then3 依然打印不出来。所以我们需要增加状态,并且保存 resolve 的值。

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;    //this 指向调用这个方法的对象实例;
    };

    //改写resolve函数,增加setTimeout;
    function resolve(value) {
    setTimeout(function() {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 0)}

    fn(resolve);
}

let p = new Promise(resolve =>{
        console.log('同步执行1');
        resolve('同步执行2');
}).then((tip) => {
    console.log("then1",tip);
}).then((tip) => {
    console.log("then2",tip);
})

setTimeout(() => {
    p.then(tip => {
        console.log('then3', tip);
    })
});

运行结果如下

image

三、增加状态

为了解决上一节抛出的问题,我们必须加入状态机制,也就是大家熟知的pendingfulfilledrejected

Promises/A+规范中的2.1Promise States中明确规定了,pending可以转化为fulfilledrejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilledrejected状态只能由pending转化而来,两者之间不能互相转换。如下图所示:

image

增加状态后的Promise如下

function Promise(fn) {
    var state = 'pending',
        value = null,
        callbacks = [];
    this.then = function (onFulfilled) {
        if (state === 'pending') {
            callbacks.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };
    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                callback(value);
            });
        }, 0);
    }
    fn(resolve);
}

let p = new Promise((resolve,reject) => {
    setTimeout(()=>{
        resolve('morrain');
    },1000)
}).then(value => {
    console.log('value=',value);
}).then(value => {
    console.log('value=',value);
})

运行结果如下

image

resolve 执行时,会将状态设置为 fulfilled ,并把 value 的值存起来,在此之后调用 then 添加的新回调,都会立即执行,直接返回保存的value值。

增加代码:

setTimeout(() => {
    p.then(tip => {
        console.log('then3', tip);
    })
});

改造代码如下

function Promise(fn) {
    var state = 'pending',
        value = null,
        callbacks = [];
    this.then = function (onFulfilled) {
        if (state === 'pending') {
            callbacks.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };
    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                callback(value);
            });
        }, 0);
    }
    fn(resolve);
}

let p = new Promise((resolve,reject) => {
    setTimeout(()=>{
        resolve('morrain');
    },1000)
}).then(value => {
    console.log('value=',value);
}).then(value => {
    console.log('value=',value);
})
//改造增加的代码如下:
setTimeout(() => {
    p.then(tip => {
        console.log('then3', tip);
    })
});

运行结果如下

image 

 从上述代码的调试可以看到,增加的代码

setTimeout(() => {
    p.then(tip => {
        console.log('then3', tip);
    })
});

此时的state已经是fulfilled状态了,所以,走的是

onFulfilled(value);
        return this;

这个分支并执行的。

至此,一个初具功能的Promise就实现好了,它实现了 then,实现了链式调用,实现了状态管理等等。但仔细想想,链式调用的实现只是在 then 中 return 了 this,因为是同一个实例,调用再多次 then 也只能返回相同的一个结果,这显然是不能满足我们的要求的。下面,讲述如何实现真正的链式调用。

三、链式调用

通常,我们希望的链式调用是如下这样的:

const p1 = Promise.resolve(42);
const p2 = p1.then(value => value + 1); // 返回 43
p2.then(console.log); // 输出: 43

每个 then 注册的 onFulfilled 都返回了不同的结果,层层递进,很明显在 then 方法中 return this 不能达到这个效果。引入真正的链式调用,then 返回的一定是一个新的Promise实例

真正的链式 Promise 是指在当前 Promise 达到 fulfilled 状态后,即开始进行下一个 Promise(后邻 Promise)。那么我们如何衔接当前 Promise 和后邻 Promise 呢?(这是理解 Promise 的难点

链式调用的实现

就是在then方法里面return一个promise就好啦。

 

posted @ 2026-01-27 22:03  chenlight  阅读(3)  评论(0)    收藏  举报