一文带你真正理解 Promise!

在我们使用 JavaScript 或者 TypeScript 写代码的时候,多多少少都会用到 Promise 。可以说,Promise 在 JavaScript 或 TypeScript 的代码中无处不在,比如我们熟悉的 axiosfetchasync/await 语法等,都是借助 Promise 来实现的,可见 Promise 是多么的重要。

有编程经验的朋友应该已经很熟悉 Promise 的功能了,但这里我还是简单地介绍一下 Promise 的概念以及 Promise 解决了什么问题。

Promise 的简单介绍

Promise 是用来进行异步编程的。在 Promise 出现之前,传统的异步编程靠的是监听事件和回调函数,比如:

const xhr = new XMLHttpRequest();xhr.addEventListener("load", function(data) {    console.log(data);});xhr.open("GET", "http://www.example.com/");xhr.send();

这种方式有一个致命的缺点,那就是「回调地狱」,如果我们想在第一个请求成功返回数据之后,再发送第二个请求,那就只能在第一个请求成功的回调函数中发送,以此类推,还想再发送下一个请求时,就只能在上一个请求成功的回调函数中发送:

const xhr = new XMLHttpRequest();xhr.addEventListener("load", function(data) {    console.log(data);    const xhr1 = new XMLHttpRequest();    xhr1.open("GET", "http://www.example.com/");    xhr1.send();    xhr1.addEventListener("load", function(data1) {        console.log(data1)        //... 发送第三个请求    });});xhr.open("GET", "http://www.example.com/");xhr.send();

这种代码不仅看起来很痛苦,写起来也非常痛苦。而 Promise 就是用来解决这个问题的,可以把 Promise 理解成一个容器,里面保存着某个未来才会结束的事件的结果(也就是异步操作的结果),它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),状态改变只有两种:从 pending 变为 fulfilled 或从 pending 变为 rejected ,一旦状态改变,就不会再变了。一般情况下,可以分别通过 resolve 函数使得状态变为 fulfilledreject 函数使得状态变为 rejected

我们使用 Promise 来简单改造一下上述的多个请求方式:

function request(url) {    return new Promise((resolve, reject) => {        const xhr = new XMLHttpRequest();        xhr.addEventListener("load", function (data) {            console.log(data);            resolve();        });        xhr.open("GET", url);        xhr.send();    });}// 发送第一个请求request("http://www.example.com/").then(() => {    // 发送第二个请求    return request("http://www.example1.com/");}).then(() => {    // 发送第三个请求    return request("http://www.example2.com/");});

可以看到,有了 Promise 的帮助,代码完全脱离了回调地狱的嵌套,以一种流水线的方式来承接发送请求的功能,代码结构上更加清晰。

更多关于 Promise 的介绍可以参考阮一峰老师的《ECMAScript 6入门》中的 Promise 对象。

捉摸不透的 Promise 输出顺序

简单地熟悉了 Promise 的功能之后,我们再来看看这三种 Promise

Promise.resolve()    .then(() => {        console.log(0);        return 4;    })    .then((res) => {        console.log(res);    });
Promise.resolve()    .then(() => {        console.log(1);    })    .then(() => {        console.log(2);    })    .then(() => {        console.log(3);    })    .then(() => {        console.log(5);    });
// 0 1 4 2 3 5
Promise.resolve()    .then(() => {        console.log(0);        return {            then(resolve) {                resolve(4);            },        };    })    .then((res) => {        console.log(res);    });
  Promise.resolve()    .then(() => {        console.log(1);    })    .then(() => {        console.log(2);    })    .then(() => {        console.log(3);    })    .then(() => {        console.log(5);    });
// 0 1 2 4 3 5
Promise.resolve()    .then(() => {        console.log(0);        return Promise.resolve(4);    })    .then((value) => {        console.log(value);    });
Promise.resolve()    .then(() => {         console.log(1);    })    .then(() => {        console.log(2);    })    .then(() => {        console.log(3);    })    .then(() => {        console.log(5);    });    // 0 1 2 3 4 5

这三种 Promise 的输出顺序不同之处在于 4 的位置,而决定 4 的位置的关键代码在于第一个 then 方法回调函数的返回值。

尤其是第二种,大家可能比较少见,返回的是一个具有 then 方法的对象,这个对象被称之为 thenable 对象,这个对象的 then 方法接收两个参数,resolvereject,它们都是函数,其含义和 Promise 构造函数中的 resolvereject 一样。所以后面紧跟的链式 then 方法能够接收到 4 这个值。

要想彻底搞明白这三种 Promise 的输出顺序,我们就得来手动实现一遍 Promise 的原理,看看里面到底有啥法宝。

Promise 源码实现

基础功能

万事开头难,为了让大家更有兴趣地往下看,我们先简单地实现 Promise 的基础功能。

先看一个基础功能的例子,如下:

const p = new Promise((resolve, reject) => {     resolve('fulfilled');     reject('rejected');})
p.then(value => {    console.log(value);}, reason => {    console.log(reason);})
// fulfilled

我们来分析一下这段代码都做了什么:

  1. Promise 构造函数会传入一个回调函数作为参数,回调函数又会接收两个参数 — resolvereject,它们都是函数,前文说过,resolvereject 是用来改变 Promise 实例对象的状态。
  2. 一旦状态改变,就不会再改变了。状态改变之后,会调用 then 方法的两个回调函数中的一个,并且会相对应地接收 resolvereject 函数的参数值。
  3. fulfilled 状态下会执行 then 方法的第一个回调函数,rejected 状态会执行第二个回调函数。

理解了这段代码的执行逻辑之后,我们分两步来实现 Promise 的基础功能,分别是: new Promise 的实现原理和 then 方法的实现原理。

new Promise 的实现原理

// 定义状态const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";
class SelfPromise {    // 储存状态,初始值是 pending    status = PENDING;    // 成功之后的值    value = null;    // 失败之后的原因    reason = null;        constructor(executor) {        // 将 resolve 和 reject 传给 new Promsie 的回调函数        executor(this.resolve, this.reject);    }        // 箭头函数可以使函数里面的 this 始终指向 Promise 实例对象    resolve = (value) => {        // 只有状态是 pending 的情况下,才改变为 fulfilled 状态        if (this.status === PENDING) {            this.status = FULFILLED;            this.value = value;        }    };
    reject = (reason) => {        // 只有状态是 pending 的情况下,才改变为 rejected 状态        if (this.status === PENDING) {            this.status = REJECTED;            this.reason = reason;        }    };}

这里有两个注意点:

  1. resolvereject 方法只有是在箭头函数的情况下,才能直接传递给 executor 函数作为参数(executor(this.resolve, this.reject)),这样在外部调用 resolve 或  reject 函数的时候,它们的 this 指向始终是 Promise 实例对象。那改为普通函数可不可以呢?也可以,不过就是需要使用 bind 方法来稳定 resolverejectthis 指向 — executor(this.resolve.bind(this), this.reject.bind(this))
  2. resolvereject 只有在 pending 状态下,才需要改变状态和记录结果,这样就达到了 Promise 状态一旦改变就不能再改变的效果。

then 方法的实现原理

//... 省略部分代码
class SelfPromise {    //... 省略部分代码        then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            // 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。            onFulfilled(this.value);        } else if (this.status === REJECTED) {            // 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。            onRejected(this.reason);        }    }}

then 方法的实现相对来说比较简单,只需要根据状态调用相应的回调函数即可。

基础功能的完整代码

// 定义状态const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";
class SelfPromise {    // 储存状态,初始值是 pending    status = PENDING;    // 成功之后的值    value = null;    // 失败之后的原因    reason = null;        constructor(executor) {        // 将 resolve 和 reject 传给 new Promsie 的回调函数        executor(this.resolve, this.reject);    }        // 箭头函数可以函数里面的 this 始终指向 Promise 实例对象    resolve = (value) => {        // 只有状态是 pending 的情况下,才改变为 fulfilled 状态        if (this.status === PENDING) {            this.status = FULFILLED;            this.value = value;        }    };
    reject = (reason) => {        // 只有状态是 pending 的情况下,才改变为 rejected 状态        if (this.status === PENDING) {            this.status = REJECTED;            this.reason = reason;        }    };        then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            // 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。            onFulfilled(this.value);        } else if (this.status === REJECTED) {            // 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。            onRejected(this.reason);        }    }}

用一开始的 Promise 基础功能的例子来测试一下:

const p = new SelfPromise((resolve, reject) => {    resolve("fulfilled");    reject("rejected");});
p.then(value => {    console.log(value);}, reason => {    console.log(reason);})
// fulfilled

撒花~ 完美!Promise 基础功能的实现原理顺利完成。

处理异步逻辑

基础版的 Promise 有一个很大的缺点,就是处理不了异步的情况。

const p = new SelfPromise((resolve, reject) => {    setTimeout(() => {        resolve("fulfilled");    });});
p.then(value => {    console.log(value);}, reason => {    console.log(reason);})
// 不会输出任何信息

由于使用了 setTimeout 执行 resolve 函数,导致 then 方法执行比 resolve 函数要早,所以 then 方法在执行的时候,Promise 的状态是 pending,不会执行任何回调函数。

基于这种情况,我们需要在 then 方法中添加处理 pending 状态的逻辑。

实现思路是:如果在 then 方法中判断到状态是 pending,那么就先将两个回调函数保存起来,然后在 Promise 内部的 resolvereject 方法中执行。

//... 省略部分代码class SelfPromise {+   // 保存 onFulfilled 回调函数+   onFulfilledCallback = null;+   // 保存 onRejected 回调函数+   onRejectedCallback = null;        //... 省略部分代码        resolve = (value) => {        if (this.status === PENDING) {            this.status = FULFILLED;            this.value = value;+           // 执行 onFulfilled 回调函数+           this.onFulfilledCallback && this.onFulfilledCallback(value);        }    };
    reject = (reason) => {        if (this.status === PENDING) {            this.status = REJECTED;            this.reason = reason;+           // 执行 onRejected 回调函数+           this.onRejectedCallback && this.onRejectedCallback(reason);        }    };        then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            onFulfilled(this.value);        } else if (this.status === REJECTED) {            onRejected(this.reason);+       } else {+           // pending 状态下保存回调函数+           this.onFulfilledCallback = onFulfilled;+           this.onRejectedCallback = onRejected;        }    }}

再运行一下用例,我们就可以打印出 fulfilled 信息了。

then 方法的多次调用

别忘了,Promise 实例对象的 then 方法是可以多次调用的,而我们现在的代码是无法做到这一点的:

const p = new SelfPromise((resolve, reject) => {    setTimeout(() => {        resolve("fulfilled");    });});
p.then((value) => {    console.log("1", value);});
p.then((value) => {    console.log("2", value);});
p.then((value) => {    console.log("3", value);});
// 3 fulfilled

目前的代码只能输出 3 fulfilled,需要把 1 fulfilled2 fulfilled 的输出补上。至于为什么只能输出 3 fulfilled 呢?关键在于源码当中 then 方法保存回调函数的方式:

class SelfPromise {    //... 省略部分代码        then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            onFulfilled(this.value);        } else if (this.status === REJECTED) {            onRejected(this.reason);        } else {            // pending 状态下保存回调函数            this.onFulfilledCallback = onFulfilled;            this.onRejectedCallback = onRejected;        }    }}

回调函数会直接保存在 this.onFulfilledCallbackthis.onRejectedCallback 中,这样就会导致保存的是最后一个 then 方法的回调函数,所以这里不能直接用两个变量来保存,而是用两个数组来保存所有的回调函数,同时 Promise 内部的 resolvereject 方法也需要循环调用所有的回调函数。

//... 省略部分代码class SelfPromise {+   // 保存所有的 onFulfilled 回调函数+   onFulfilledCallbacks = [];+   // 保存所有的 onRejected 回调函数+   onRejectedCallbacks = [];        //... 省略部分代码        resolve = (value) => {        if (this.status === PENDING) {            this.status = FULFILLED;            this.value = value;+           // 执行所有的 onFulfilled 回调函数+           this.onFulfilledCallbacks.forEach((fn) => fn(value));        }    };
    reject = (reason) => {        if (this.status === PENDING) {            this.status = REJECTED;            this.reason = reason;+           // 执行 onRejected 回调函数+           this.onRejectedCallbacks.forEach((fn) => fn(reason));        }    };        then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            onFulfilled(this.value);        } else if (this.status === REJECTED) {            onRejected(this.reason);+       } else {+           // pending 状态下保存所有的回调函数+           this.onFulfilledCallbacks.push(onFulfilled);+           this.onRejectedCallbacks.push(onRejected);        }    }}

细心的朋友应该发现了这其实是一个「观察者模式」then 方法的 this.onFulfilledCallbacks.push(onFulfilled)this.onRejectedCallbacks.push(onRejected) 就是在添加订阅者,而 resolvereject 方法就是在通知所有的订阅者。

我们再运行一下用例,得出结果:

1 fulfilled2 fulfilled3 fulfilled

完美,令人满意的结果!

then 方法的链式调用

Promise 最核心的功能就是 then 方法的链式调用,这也是解决回调地狱的关键所在。就目前我们手动实现的代码来看,是不能够进行 then 方法的链式调用的,因为 then 方法没有任何返回值。

要想实现 then 方法的链式调用,then 方法必须返回 Promise 对象,并且下一个 then 方法的回调函数的参数会依赖上一个 then 方法的回调函数的返回值,这种依赖有两种情况:

  1. 如果返回的是 Promise 实例对象,那么下一个 then 方法的回调函数会接收该实例对象的 resolvereject 函数传入的值,比如:

    const p = new Promise((resolve, reject) => {    resolve(1);});p.then((value) => {    console.log(value);    return new Promise((resolve, reject) => {        resolve(2);        // reject(2);    });}).then((value) => {    console.log("fulfilled", value);}, (err) => {    console.log("rejected", err);});
    // 1// fulfilled 2
    // 如果调用的是 reject(2),那么返回的是:// 1// rejected 2
  2. 如果返回的是 thenable 对象,会和第一种情况一样:

    const p = new Promise((resolve, reject) => {    resolve(1);});p.then((value) => {    console.log(value);    return {        then(resolve, reject) {            resolve(2);            // reject(2);        }    };}).then((value) => {    console.log("fulfilled", value);}, (err) => {    console.log("rejected", err);});
    // 1// fulfilled 2
    // 如果调用的是 reject(2),那么返回的是:// 1// rejected 2
  3. 如果返回的是其他对象或者原始数据类型的值,那么下一个 then 方法的回调函数的参数会直接接收这个值,比如:

    const p = new Promise((resolve, reject) => {    resolve(1);});p.then((value) => {    console.log(value);    return {        value: 2,    };}).then((value) => {    console.log("fulfilled", value);});
    // 1// fulfilled { value: 2 }

了解了 then 方法链式调用的基本情况之后,我们来动手实现一下 then 方法的链式调用。

首先,让 then 方法返回一个 Promise 对象:

class SelfPromise {  // ... 省略部分代码
    then(onFulfilled, onRejected) {+       const promise2 = new SelfPromise((resolve, reject) => {            if (this.status === FULFILLED) {                onFulfilled(this.value);            } else if (this.status === REJECTED) {                onRejected(this.reason);            } else {                this.onFulfilledCallbacks.push(onFulfilled);                this.onRejectedCallbacks.push(onRejected);            }+       });+       // 返回 Promise 对象+       return promise2;    }}

接着,通过 resolvereject 函数来改变 promise2 对象的状态,并且建立上一个 then 方法与下一个 then 方法的依赖关系。

class SelfPromise {  // ... 省略部分代码
    then(onFulfilled, onRejected) {        const promise2 = new SelfPromise((resolve, reject) => {            if (this.status === FULFILLED) {+               // 获取上一个 then 方法的 fulfilled 回调函数的返回值+               const v = onFulfilled(this.value);+               // 根据返回值,改变 promise2 的状态,并建立与下一个 then 方法的关系+               resolvePromise(v, resolve, reject);            } else if (this.status === REJECTED) {+               // 获取上一个 then 方法的 rejected 回调函数的返回值+               const v = onRejected(this.reason);+               //根据返回值,改变 promise2 的状态,并建立与下一个 then 方法的关系+               resolvePromise(v, resolve, reject);            } else {                this.onFulfilledCallbacks.push(onFulfilled);                this.onRejectedCallbacks.push(onRejected);            }        });
        return promise2;    }}
function resolvePromise(value, resolve, reject) {    if (typeof value === "object" || typeof value === "function") {        if (value === null) {            // 如果返回值是 null,            // 直接调用 resolve 函数,promise2 的状态变为 fulfilled,            // 返回值由下一个 then 方法的第一个回调函数接收。            return resolve(value);        }        try {            if (typeof value.then === "function") {                // 如果返回值是 Promise 对象或者 thenable 对象                // 那就只能交给它们的 then 方法来改变 promise2 的状态,以及获取相对应的状态值                // 以下代码等同于 value.then((value) => resolve(value), (err) => reject(err))                value.then(resolve, reject);            } else {                // 如果 then 不是函数,同 null 情况一样的处理逻辑。                resolve(value);            }        } catch (error) {            // 出现异常的情况下,调用 reject 函数            // promise2 的状态变为 rejected,            // 错误信息由下一个 then 方法的第二回调函数接收            reject(error);        }    } else {        // 如果返回值是其他对象或者原始数据类型值,同 null 情况一样的处理逻辑。        resolve(value);    }}

这里比较难理解的应该是,返回值为 Promise 对象或者 thenable 对象的处理情况 — value.then(resolve, reject),这段代码写完整一点就是 value.then((value) => resolve(value), (err) => reject(err)),这里 then 方法的回调函数中 value 参数值和 err 参数值就是 Promise 对象或者 thenable 对象内部调用 resolve 或者 reject 函数传入的参数值,再把这些值传递给 promise2resolvereject 函数,从而达到改变 promise2 的状态,下一个 then 方法的回调函数也会被调用并且接收到这些值。

总得来说就是,promise2 的状态完全由返回值(Promise 对象或者 thenable 对象)来控制。就跟以下这段代码一样:

const promise2 = new SelfPromise((resolve2, reject2) => {    const value = new SelfPromise((resolve, reject) => {        resolve(1);    });    value.then(        (v) => resolve2(v),        (err) => reject2(err)    );});
promise2.then((value) => {  console.log(value);});
// 1

理解清楚上述的逻辑之后,执行一下测试用例。

const p = new SelfPromise((resolve, reject) => {  resolve(1);});
p.then((value) => {  console.log(value);  return new SelfPromise((resolve) => {    resolve(2);  });}).then((value) => {  console.log(value);});
// 1// 2
p.then((value) => {  console.log(value);  return {    then(resolve) {      resolve(2);    }  };}).then((value) => {  console.log(value);});
// 1// 2
p.then((value) => {  console.log(value);  return 2;  };}).then((value) => {  console.log(value);});
// 1// 2

令人满意的结果~

那如果 then 方法是返回自身的 Promise 对象该怎么办?我们来看看原生的 Promise 是怎么处理的:

const p = new Promise((resolve, reject) => {  resolve(1);});const p1 = p.then(value => {  console.log(value);  return p1;});
// 1// Uncaught (in promise) TypeError: Chaining cycle detected for promise #

报错,错误信息是:会发生 Promise 循环调用。

所以,我们需要改造一下 SelfPromise 的代码,来模拟这种报错的效果:

function resolvePromise(promise2, value, resolve, reject) {+   // 如果 then 方法返回的是自身 Promise 对象,返回错误信息+   if (promise2 === value) {+       return reject(new TypeError('Chaining cycle detected for promise #'));+   }    if (typeof value === "object" || typeof value === "function") {        if (value === null) {            return resolve(value);        }        try {            if (typeof value.then === "function") {                value.then(resolve, reject);            } else {                resolve(value);            }        } catch (error) {            reject(error);        }    } else {        resolve(value);    }}
class SelfPromise {  // ... 省略部分代码
    then(onFulfilled, onRejected) {        const promise2 = new SelfPromise((resolve, reject) => {            if (this.status === FULFILLED) {                const v = onFulfilled(this.value);+               // 将 promise2 传入进行判断+               resolvePromise(promise2, v, resolve, reject);            } else if (this.status === REJECTED) {                const v = onRejected(this.reason);+               // 将 promise2 传入进行判断+               resolvePromise(promise2, v, resolve, reject);            } else {                this.onFulfilledCallbacks.push(onFulfilled);                this.onRejectedCallbacks.push(onRejected);            }        });
        return promise2;    }}

再测试一下前面的例子,得出结果:

// 1// Uncaught ReferenceError: Cannot access 'p1' before initialization

尴尬,错误信息完全不一样,这里根据提示可以知道,我们在 p1 定义之前就使用了它。实际情况也确实如此,我们是先等 then 方法里面的回调函数执行完毕之后,then 方法再返回 Promise 对象,但我们却在回调函数内先用了这个 Promise 对象,所以才报的这个错误信息。

那怎么办呢?其实只需要把 then 方法的回调函数的同步执行改为异步执行就可以了。对原生 Promise 足够了解的朋友应该知道,then 方法的回调函数是微任务,创建微任务的方式有以下这几种:

  1. 浏览器环境下有 MutationObserver
  2. Promise.then()
  3. Node 环境下有 process.nextTick
  4. queueMicrotask

由于 Promise.then 是我们自己要手动实现的,MutationObserverprocess.nextTick 又需要在专门的环境下使用,所以这里选择使用 queueMicrotask 来实现微任务的创建。

可以在 MDN 文档上了解更多关于 queueMicrotask 的信息。

我们再来改造一下 SelfPromise 的代码:

class SelfPromise {    // ... 省略部分代码        then(onFulfilled, onRejected) {        const promise2 = new SelfPromise((resolve, reject) => {+           const fulfilledMicrotask = () => {+               queueMicrotask(() => {+                   const v = onFulfilled(this.value);+                   resolvePromise(promise2, v, resolve, reject);+               });+           };
+           const rejectedMicrotask = () => {+               queueMicrotask(() => {+                   const v = onRejected(this.reason);+                   resolvePromise(promise2, v, resolve, reject);+               });+           };                    if (this.status === FULFILLED) {+               // 异步执行 fulfilled 回调函数+               fulfilledMicrotask();            } else if (this.status === REJECTED) {+               // 异步执行 rejected 回调函数+               rejectedMicrotask();            } else {+               // 添加订阅者(异步执行的回调函数)+               this.onFulfilledCallbacks.push(fulfilledMicrotask);+               this.onRejectedCallbacks.push(rejectedMicrotask);            }        });
        return promise2;    }}

测试一下:

const p = new SelfPromise((resolve, reject) => {    resolve('1')}) // 返回自身 Promise 对象const p1 = p.then(value => {   console.log(value);   return p1;}) // 接收错误信息p1.then(value => {    console.log(2);    console.log('fulfilled', value)}, err => {    console.log(3);    console.log('reject', err.message);})// 1// 3// reject Chaining cycle detected for promise #

效果终于达到了!

thenable 对象的处理补充

通过对 then 方法链式调用的实现,我们可以知道:then 方法的回调函数是异步执行的,属于微任务。

回想一下前文说到的 then 方法返回 Promise 对象或 thenable 对象的处理方式:

function resolvePromise(promise2, value, resolve, reject) {    // 部分省略代码...    if (typeof value === "object" || typeof value === "function") {         // 部分省略代码...        try {            if (typeof value.then === "function") {                value.then(resolve, reject);            } else {                resolve(value);            }        } catch (error) {            reject(error);        }    } else {        resolve(value);    }}

从这段代码上看,Promise 对象和 thenable 对象处理方式是一样的,但是,别忘了,Promise 对象的 then 方法的回调函数是异步执行的,而 thenable 对象的 then 方法 的回调函数可能是同步的也可能是异步的,因为 thenable 对象属于开发人员自定义的对象,是否异步完全由开发人员自己处理。

所以,为了能够让它稳定异步执行,我们需要把它放到 queueMicrotask 的回调函

function resolvePromise(promise2, value, resolve, reject) {    // 部分省略代码...    if (typeof value === "object" || typeof value === "function") {         // 部分省略代码...        try {            if (typeof value.then === "function") {+               // 异步执行+               queueMicrotask(() => {+                   value.then(resolve, reject);+               });            } else {                resolve(value);            }        } catch (error) {            reject(error);        }    } else {        resolve(value);    }}

thenable 对象添加上了异步执行,是不是就完美了呢?当然不是,刚刚说到了,thenable 对象属于开发人员自定义的对象,所以,我们还需要处理以下这三点:

  1. thenable 对象可能被开发人员通过 Object.defineProperty()new Proxy()then 方法设置了一层代理(很多库或者框架的作者都会这么做),为了避免在调用 then 方法的时候,触发代理逻辑,所以需要先将函数取出来存放到一个变量上,再通过 call 方法进行调用,保持 this 指向是 thenable 对象。

    function resolvePromise(promise2, value, resolve, reject) {    // 部分省略代码...    if (typeof value === "object" || typeof value === "function") {        // 部分省略代码...+       // 将函数取出并存到一个常量上+       const then = value.then;        try {+           if (typeof then === "function") {                queueMicrotask(() => {+                   then.call(value, resolve, reject);                });            } else {                resolve(value);            }        } catch (error) {            reject(error);        }    } else {        resolve(value);    }}
  2. thenable 对象可能会同时调用 resolvereject 回调函数,所以我们需要控制,只要调用其中一个回调函数,就不会再调用另一个回调函数,跟 Promise 内部的 resolvereject 回调函数一样。

    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    function resolvePromise(promise2, value, resolve, reject) {   // 部分省略代码...   if (typeof value === "object" || typeof value === "function") {       // 部分省略代码...+       // called 变量控制 thanable 对象只调用 resolve 或 reject 函数一次+       let called = false;       // 将函数取出并存到一个常量上       const then = value.then;       try {           if (typeof then === "function") {               queueMicrotask(() => {                   then.call(                       value,+                       (value2) => {+                           if (called) return;+                           // 调用了 resolve,called 设为 true,防止再一次调用 reject+                           called = true;+                           resolve(value);+                       },+                       (err) => {+                           if (called) return;+                           // 调用了 reject,called 设为 true,防止再一次调用 reolve+                           called = true;+                           reject(err);+                       }                   );               });           } else {               resolve(value);           }       } catch (error) {+          if (called) return;+          // 错误处理,会调用 reject,called 设为 true,防止再一次调用 reolve+          called = true;           reject(error);       }   } else {       resolve(value);   }}
  3. 在某些情况下,thenable 对象中的 resolve 回调函数很有可能传入的是 Promise 对象或者 thenable 对象,比如:

    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    const p = new Promise((resolve, reject) => {    resolve(1);});const thenable = {    then(resolve) {        resolve(p);    }};Promise.resolve(thenable).then((value) => {    console.log(value);});// 1

    所以,要递归调用 resolvePromise 函数处理这种情况。

    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    function resolvePromise(promise2, value, resolve, reject) {    // 部分省略代码...    if (typeof value === "object" || typeof value === "function") {        // 部分省略代码...        // called 变量控制 thanable 对象只调用 resolve 或 reject 函数一次        let called = false;        // 将函数取出并存到一个常量上        const then = value.then;        try {            if (typeof then === "function") {                queueMicrotask(() => {                    then.call(                        value,                        (value2) => {                            if (called) return;                            // 调用了 resolve,called 设为 true,防止再一次调用 reject                            called = true;+                           // value2 可能是 Promise 对象,所以需要调用 resolvePromise 函数来进行处理+                           resolvePromise(promise2, value2, resolve, reject);                        },                        (err) => {                            if (called) return;                            // 调用了 reject,called 设为 true,防止再一次调用 reolve                            called = true;                            reject(err);                        }                    );                });            } else {                resolve(value);            }        } catch (error) {            if (called) return;            // 错误处理,会调用 reject,called 设为 true,防止再一次调用 reolve            called = true;            reject(error);        }    } else {        resolve(value);    }}

特殊的 resolve 函数参数

前面说到了,thenable 对象中的 resolve 回调函数很有可能传入的是 Promise 对象或 thenable 对象,同理,Promise 构造函数中的 resolve 函数也会有这种情况。而在该情况下,resolve 函数变成 fulfilled 状态的机制完全交给参数(Promise 对象或 thenable 对象)决定,比如:

const p = new SelfPromise((resolve, reject) => {    resolve(SelfPromise.reject(1));    reject(2);});p.then((value) => {    console.log("onFulfilled", value);}).catch((err) => {    console.log("rejected", err);});// rejected 1

处理的方式跟 thenable 对象一样,再次调用 resolvePromise 函数,只不过第一个参数是 null 或者 undefined,因为这是本次 Promise 对象的处理情况,而不是 then 方法返回的 Promise 对象。

// ...省略部分代码class SelfPromise {    then(onFulfilled, onRejected) {        const promise2 = new SelfPromise((resolve, reject) => {            const fulfilledMicrotask = () => {                queueMicrotask(() => {                    try {+                       if (this.value && typeof this.value.then === "function") {+                           // 如果 resolve 函数传入的值是 Promise 对象或 thenable 对象+                           resolvePromise(null, this.value, onFulfilled, onRejected);+                       } else {                            // 获取上一个 then 方法的 fulfilled 回调函数的返回值                            const v = onFulfilled(this.value);                            // 根据返回值,改变 promise2 的状态,并建立与下一个 then 方法的关系                            resolvePromise(promise2, v, resolve, reject);+                       }                    } catch (error) {                        reject(error);                    }                });            };        });        return promise2;    }}

错误捕获的补充

我们在所有可能发生代码运行错误的地方套上 try...catch,然后再调用 reject 函数接收错误:

// ...省略部分代码class SelfPromise {    // ...省略部分代码    constructor(executor) {+       try {            executor(this.resolve, this.reject);+       } catch (error) {+           this.reject(error);+       }    }    // ...省略部分代码        then(onFulfilled, onRejected) {        const promise2 = new SelfPromise((resolve, reject) => {            const fulfilledMicrotask = () => {                queueMicrotask(() => {+                   try {                        const v = onFulfilled(this.value);                        resolvePromise(promise2, v, resolve, reject);+                   } catch (error) {+                       reject(error);+                   }                });            };                        const rejectedMicrotask = () => {                queueMicrotask(() => {+                   try {                        const v = onRejected(this.reason);                        resolvePromise(promise2, v, resolve, reject);+                   } catch (error) {+                       reject(error);+                   }                });            };        });                return promise2;    }}

then 方法的值穿透

什么是 then 方法的值穿透?就是在 then 方法链式调用的情况下,如果有一个 then 方法不传入 fulfilled 回调函数,那么会一直传给下一个 then 方法,直到这个 then 方法有 fulfilled 回调函数。比如:

const p = new Promise((resolve, reject) => {    resolve(1);})
p.then().then().then(value => console.log(value));
// 1

不传入 rejected 回调函数的情况也是一样:

const p = new Promise((resolve, reject) => {    reject(2);});
p.then((value) => console.log("fulfilled", value)).then(    (valu1e) => console.log("fulfilled", value1),    (reason) => console.log("rejected", reason));// rejected 2

then 方法的值穿透的原理是什么呢?其实主要靠 then 方法的链式调用来实现的,上面第一种情况的代码等同于:

const p = new Promise((resolve, reject) => {    resolve(1);})
p.then((value) => value)    .then((value) => value)    .then(value => console.log(value));

只不过 Promise 内部帮我们做了这件事:如果 then 的第一个参数不传 fulfilled 回调函数或者是非函数类型的数据,那就将第一个参数的值设置为 (value) => value;同理第二个参数的值设置为 (reason) => throw reason,抛出错误,就可以调用 reject 函数接收错误。

接下来,我们自己动手实现一下:

// ...省略部分代码class SelfPromise {    // ...省略部分代码    then(onFulfilled, onRejected) {        const promise2 = new SelfPromise((resolve, reject) => {+           // onFulfilled 回调函数的默认值,then 方法值传递的原理+           onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;+           // onRejected 回调函数的默认值,then 方法值传递的原理+           onRejected = typeof onRejected === "function" ? onRejected : (reason) => { reject(reason); };
            // ...省略部分代码        });        return promise2;    }}

再运行上面的例子,你会发现结果是一模一样的。

静态方法 resolve 和 reject 的实现

静态 resolve 方法的实现原理

我们经常使用 Promise.resolve() 来生成一个状态是 fulfilledPromise 对象,该方法的参数有三种:

  1. 参数可以是一个 Promise 实例对象。

    如果参数是 Promise 实例对象,那么 Promise.resolve 方法将原封不动地返回这个对象。

  2. 参数可以是一个 thenable 对象

    如果参数是 thenable 对象,那么在 Promise.resolve 内部会将该对象的 then 方法放入微任务队列中执行。比如:

    const thenable = {    then(resolve, reject) {        console.log(1);        resolve(2);    }};
    Promise.resolve(thenable).then((value) => {    console.log(value);});
    console.log(3);
    // 3// 1// 2
  3. 不传参数或者参数是其他类型的数据,Promise.resolve 方法返回 fulfilled 状态的 Promise 实例对象。

基于以上的特点,我们来实现一下它的原理:

// ...省略部分代码class SelfPromise {    // ...省略部分代码        static resolve(param) {        // 如果参数是 Promise 实例对象,原封不动地返回这个对象        if (param instanceof SelfPromise) {          return param;        }                return new SelfPromise((resolve, reject) => {          if (param && typeof param.then === "function") {              // 如果参数是 thenable 对象,放入微任务队列中执行              queueMicrotask(() => {                  param.then(resolve, reject);              });          } else {              // 其他情况直接调用 resolve 函数,返回 fulfilled 状态的 Promise 对象              resolve(param);          }        });    }}

静态 reject 方法的实现原理

Promise.reject 方法也是一个 Promise 对象,状态是 rejected,但它的参数类型并没有 Promise.resolve 方法那么复杂,只有一种情况:无论传入什么类型的数据,都只会返回 rejected 状态的 Promise 实例对象。

实现原理也比较简单:

// ...省略部分代码class SelfPromise {    // ...省略部分代码        static reject(param) {        return new SelfPromise((resolve, reject) => {            reject(param);        });    }}

Promise 实例对象方法的实现

Promise 实例对象的方法除了 then 方法,还有 catch 方法和 finally 方法,接下来,我们也手动实现一下这两个方法。

catch 方法实现原理

catch 方法是用于指定发生错误时的回调函数,它其实就是对 then 方法的调用,想想我们之前是通过 then 方法的第二个参数来接收 rejected 状态的错误:

const p = new SelfPromise((resolve, reject) => {    reject(1);})p.then((value) => {    console.log("fulfilled", value);}, (reason) => {    console.log("rejected", reason);});
// rejected 1

所以, catch 方法等同于 then(null, onRejected)then(undefined, onRejected),因此实现原理也很明了:

// ...省略部分代码class SelfPromise {    // ...省略部分代码    catch(onRejected) {        return this.then(null, onRejected);    }}

finally 方法实现原理

finally 方法用于在 Promise 对象的状态是 fulfilled 还是 rejected 的情况下,都会执行的操作。注意,finally 方法的回调函数不接受任何参数,同时,finally 方法也会返回 Promise 对象。

finally 中文翻译过来是最终的意思,所以,finally 方法的回调函数无论返回什么值,都不会传递给后面链式调用的 then 方法的回调函数。并且,finally 方法自带值穿透特性,会将前面 then 方法回调函数返回的值自动传给它后面的 then 方法或者 catch 方法的回调函数。比如:

Promise.reject(1)    .finally((reason) => {        console.log("finally", reason);        return 2;    })    .then((value) => {        console.log("then", value);    })    .catch((reason) => {        console.log("catch", reason);    });
// finally undefined// catch 1

finally 本质上也是 then 方法的特例,由于 finally 方法的回调函数跟前面 Promise 对象的状态无关,所以,就等同于需要在 then 方法中的两个回调函数里调用 finally 方法的回调函数,并且基于 finally 自身的一些特性,还需要借助静态方法 resolve 来实现其原理:

// ...省略部分代码class SelfPromise {    // ...省略部分代码    finally(callback) {        return this.then(            // 值穿透以及 callback() 返回值不会传递给后面 then 方法的原理            (value) => SelfPromise.resolve(callback()).then(() => value),            (reason) => SelfPromise.resolve(callback()).then(() => { throw reason; })        );    }}

静态方法 all,race,allSettled 和 any 的实现

Promise.all 的实现原理

Promise.all 方法接收一个具有 Iterator 接口的数据作为参数,参数中的每一个元素都是 Promise 对象,如果不是,就会用 Promise.resolve 方法将它转换为 Promise 对象。

Promise.all 方法返回的也是一个新的 Promise 对象,当所有元素的状态都是 fulfilled 时,返回的新 Promise 对象的状态才是 fulfilled,否则就是 rejected

如果新 Promise 对象的状态是 fulfilled,那么用 then 方法接收的结果是一个数组,数组中结果的顺序就是 Promise.all 方法参数的顺序,比如:

Promise.all([    Promise.resolve(1),    Promise.resolve(2),    Promise.resolve(3),]).then((res) => {    console.log(res);});
// [1, 2, 3]

原理实现思路:

  1. 首先要判断参数的 Symbol.iterator 属性是否是函数,如果是,这表明参数是具有 Iterator 接口的数据,如果不是直接返回错误。
  2. 统一遍历具有 Iterator 接口的数据的方法,因为需要考虑到 SetMapString 等类型数据。
  3. 当每个 Promise 对象元素的状态变为 fulfilled 时,会将其结果存到对应索引的数组中。
// ...省略部分代码class SelfPromise {    // ...省略部分代码    static all(promiseIterator) {        return new SelfPromise((resolve, reject) => {            // 判断参数是否是具有 `Iterator` 接口的数据            if (                promiseIterator &&                typeof promiseIterator[Symbol.iterator] === "function"            ) {                const res = []; // 结果数组                let countRes = 0; // 记录数组中结果的个数                const len = promiseIterator.length || promiseIterator.size;
                // 保存对应索引的结果                function saveRes(value, index) {                    res[index] = value;                    if (++countRes === len) {                        resolve(res);                    }                }                // 返回迭代器对象                const iterator = promiseIterator[Symbol.iterator]();                // 遍历具有迭代器的数据结构,并且记录索引值                for (                    let i = 0, iteratorRes = iterator.next();                    iteratorRes.done !== true;                    i++, iteratorRes = iterator.next()                ) {                    SelfPromise.resolve(iteratorRes.value).then((value) => {                        // 在对应索引位置上保存结果                        saveRes(value, i);                    }, reject);                }            } else {                reject(new TypeError("Arguments is not iterable"));            }        });    }}

Promise.race 的实现原理

Promise.race 方法接收的参数和返回值同 Promise.all 一样,它的特点是:哪个 Promise 实例对象的状态改变得快,Promise.race 方法最后就是什么状态,并且那个率先改变状态的 Promise 实例对象的返回值,会传递给 Promise.race 方法返回值的 then 方法或 catch 方法。

举个例子:

const p1 = new Promise((resolve, reject) => {    setTimeout(() => {      resolve(1);    }, 1000);});const p2 = new Promise((resolve, reject) => {    setTimeout(() => {      reject(2);    }, 2000);});Promise.race([p1, p2]).then((res) => {    console.log(res);});
// 一秒过后,输出 1,状态为 fulfilled

实现原理跟 Promise.all 方法差不多,只不过不再需要把结果保存到数组上了。

// ...省略部分代码class SelfPromise {    // ...省略部分代码    static race(promiseIterator) {        return new SelfPromise((resolve, reject) => {            if (                promiseIterator &&                typeof promiseIterator[Symbol.iterator] === "function"            ) {                // 返回迭代器对象                const iterator = promiseIterator[Symbol.iterator]();                // 遍历具有迭代器的数据结构                for (                    let iteratorRes = iterator.next();                    iteratorRes.done !== true;                    iteratorRes = iterator.next()                ) {                    // 哪个 Promise 对象状态改变得快,race 方法最后就是什么状态                    SelfPromise.resolve(iteratorRes.value).then(                        resolve,                        reject                    );                }            } else {                reject(new TypeError("Arguments is not iterable"));            }        });    }}

Promise.allSettled 的实现原理

Promise.allSettled 方法接收的参数同 Promise.all 一样,不过 Promise.allSettled 方法只有在所有 Promise 对象都发生状态改变了(无论是 fulfilled 还是 rejected),返回的新 Promise 实例对象状态才会改变,并且状态总是为 fulfilled

实现原理和 Promise.all 方法基本一样,只不过在每个 Promise 对象上的 rejected 状态的回调函数处理不同。

// ...省略部分代码class SelfPromise {    // ...省略部分代码    static allSettled(promiseIterator) {        return new SelfPromise((resolve, reject) => {            if (                promiseIterator &&                typeof promiseIterator[Symbol.iterator] === "function"            ) {                const res = [];                let countRes = 0;                const len = promiseIterator.length || promiseIterator.size;
                function saveRes(value, index) {                    res[index] = value;                    if (++countRes === len) {                        resolve(res);                    }                }                // 返回迭代器对象                const iterator = promiseIterator[Symbol.iterator]();                // 遍历具有迭代器的数据结构,并且记录索引值                for (                    let i = 0, iteratorRes = iterator.next();                    iteratorRes.done !== true;                    i++, iteratorRes = iterator.next()                ) {                    SelfPromise.resolve(iteratorRes.value)                        .then((value) => {                            saveRes({ status: "fullfilled", value }, i);                        })                        .catch((reason) => {                            saveRes({ status: "rejected", reason }, i);                        });                }            } else {                reject(new TypeError("Arguments is not iterable"));            }        });    }}

Promise.any 的实现原理

Promise.any 方法接收的参数同 Promise.all 一样,只要其中一个 Promise 实例对象的状态变成 fulfilled,那么Promise.any 方法返回的新 Promise 实例对象的状态就是 fulfilled,如果所有 Promise 实例对象都变成 rejected 状态,返回的新 Promise 实例对象的状态才会变成 rejected 状态。

在实现原理上,有一点是跟 Promise.all 方法反过来的,那就是每个 Promise 实例对象在 rejected 状态才保存对应索引位置上的结果。

// ...省略部分代码class SelfPromise {    // ...省略部分代码    static any(promiseIterator) {        return new SelfPromise((resolve, reject) => {            if (                promiseIterator &&                typeof promiseIterator[Symbol.iterator] === "function"            ) {                const res = [];                let countRes = 0;                const len = promiseIterator.length || promiseIterator.size;
                function saveRes(reason, index) {                    res[index] = reason;                    if (++countRes === len) {                        const err = new AggregateError(                            res,                            "All promises were rejected"                        );                        reject(err);                    }                }                // 返回迭代器对象                const iterator = promiseIterator[Symbol.iterator]();                // 遍历具有迭代器的数据结构,并且记录索引值                for (                    let i = 0, iteratorRes = iterator.next();                    iteratorRes.done !== true;                    i++, iteratorRes = iterator.next()                ) {                    SelfPromise.resolve(iteratorRes.value).then(                        resolve,                        (reason) => {                            // 在对应索引位置上保存结果                            saveRes(reason, i);                        }                    );                }            } else {                reject(new TypeError("Arguments is not iterable"));            }        });    }}

总结

完整版的 Promise 实现原理我已经放到 Github 上了 — self-promise,里面的测试也有前面说到的捉摸不透的三种 Promise 的输出顺序。

总得来说,实现 Promise 的原理关键在于:

  1. Promise 运用了观察者模式,then 方法用于添加订阅者,resolvereject 函数用于通知所有订阅者。
  2. then 方法的回调函数是异步执行的,属于微任务,所以对于 thenable 对象中 then 方法放在 Promise 内部执行也是异步的。
  3. then 链式调用的功能在于 then 方法的返回值是 Promise 对象,对于 then 方法回调函数的返回值类型要有不同的处理方式。
  4. 静态方法 resolvereject 就是封装了创建 Promise 对象的创建过程;实例方法 catchfinally 就是对 then 方法的二次封装;静态方法 allraceallSettledany 也是利用了 then 方法的机制。

 

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=16608,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?