

中高端软件定制开发服务商

13245491521 13245491521
一文带你真正理解 Promise! 在我们使用 JavaScript 或者 TypeScript 写代码的时候,多多少少都会用到 Promise 。可以说,Promise 在 JavaScript 或 TypeScript 的代码中无处不在,比如我们熟悉的 axios 和 fetch,async/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 函数使得状态变为 fulfilled,reject 函数使得状态变为 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 5Promise.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 5Promise.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 方法接收两个参数,resolve 和 reject,它们都是函数,其含义和 Promise 构造函数中的 resolve 和 reject 一样。所以后面紧跟的链式 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我们来分析一下这段代码都做了什么: Promise 构造函数会传入一个回调函数作为参数,回调函数又会接收两个参数 — resolve 和 reject,它们都是函数,前文说过,resolve 和 reject 是用来改变 Promise 实例对象的状态。一旦状态改变,就不会再改变了。状态改变之后,会调用 then 方法的两个回调函数中的一个,并且会相对应地接收 resolve 或 reject 函数的参数值。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; } };}这里有两个注意点: resolve 和 reject 方法只有是在箭头函数的情况下,才能直接传递给 executor 函数作为参数(executor(this.resolve, this.reject)),这样在外部调用 resolve 或 reject 函数的时候,它们的 this 指向始终是 Promise 实例对象。那改为普通函数可不可以呢?也可以,不过就是需要使用 bind 方法来稳定 resolve 和 reject 的 this 指向 — executor(this.resolve.bind(this), this.reject.bind(this))。resolve 和 reject 只有在 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 内部的 resolve 或 reject 方法中执行。 //... 省略部分代码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 fulfilled 和 2 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.onFulfilledCallback 或 this.onRejectedCallback 中,这样就会导致保存的是最后一个 then 方法的回调函数,所以这里不能直接用两个变量来保存,而是用两个数组来保存所有的回调函数,同时 Promise 内部的 resolve 和 reject 方法也需要循环调用所有的回调函数。 //... 省略部分代码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) 就是在添加订阅者,而 resolve 和 reject 方法就是在通知所有的订阅者。 我们再运行一下用例,得出结果: 1 fulfilled2 fulfilled3 fulfilled完美,令人满意的结果! then 方法的链式调用Promise 最核心的功能就是 then 方法的链式调用,这也是解决回调地狱的关键所在。就目前我们手动实现的代码来看,是不能够进行 then 方法的链式调用的,因为 then 方法没有任何返回值。 要想实现 then 方法的链式调用,then 方法必须返回 Promise 对象,并且下一个 then 方法的回调函数的参数会依赖上一个 then 方法的回调函数的返回值,这种依赖有两种情况: 如果返回的是 Promise 实例对象,那么下一个 then 方法的回调函数会接收该实例对象的 resolve 或 reject 函数传入的值,比如: 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如果返回的是 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如果返回的是其他对象或者原始数据类型的值,那么下一个 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; }}接着,通过 resolve 和 reject 函数来改变 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 函数传入的参数值,再把这些值传递给 promise2 的 resolve 和 reject 函数,从而达到改变 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// 2p.then((value) = { console.log(value); return { then(resolve) { resolve(2); } };}).then((value) = { console.log(value);}); // 1// 2p.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报错,错误信息是:会发生 Promise 循环调用。 所以,我们需要改造一下 SelfPromise 的代码,来模拟这种报错的效果: function resolvePromise(promise2, value, resolve, reject) {+ // 如果 then 方法返回的是自身 Promise 对象,返回错误信息+ if (promise2 === value) {+ return reject(new TypeError('Chaining cycle detected for promise #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 方法的回调函数是微任务,创建微任务的方式有以下这几种: 浏览器环境下有 MutationObserver。Promise.then()Node 环境下有 process.nextTick。queueMicrotask由于 Promise.then 是我们自己要手动实现的,MutationObserver 和 process.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 #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 对象属于开发人员自定义的对象,所以,我们还需要处理以下这三点: 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); }}thenable 对象可能会同时调用 resolve 和 reject 回调函数,所以我们需要控制,只要调用其中一个回调函数,就不会再调用另一个回调函数,跟 Promise 内部的 resolve 和 reject 回调函数一样。 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); }}在某些情况下,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 2then 方法的值穿透的原理是什么呢?其实主要靠 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() 来生成一个状态是 fulfilled 的 Promise 对象,该方法的参数有三种: 参数可以是一个 Promise 实例对象。 如果参数是 Promise 实例对象,那么 Promise.resolve 方法将原封不动地返回这个对象。 参数可以是一个 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不传参数或者参数是其他类型的数据,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 1finally 本质上也是 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]原理实现思路: 首先要判断参数的 Symbol.iterator 属性是否是函数,如果是,这表明参数是具有 Iterator 接口的数据,如果不是直接返回错误。统一遍历具有 Iterator 接口的数据的方法,因为需要考虑到 Set,Map,String 等类型数据。当每个 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 的原理关键在于: Promise 运用了观察者模式,then 方法用于添加订阅者,resolve 和 reject 函数用于通知所有订阅者。then 方法的回调函数是异步执行的,属于微任务,所以对于 thenable 对象中 then 方法放在 Promise 内部执行也是异步的。then 链式调用的功能在于 then 方法的返回值是 Promise 对象,对于 then 方法回调函数的返回值类型要有不同的处理方式。静态方法 resolve 和 reject 就是封装了创建 Promise 对象的创建过程;实例方法 catch 和 finally 就是对 then 方法的二次封装;静态方法 all,race,allSettled,any 也是利用了 then 方法的机制。 阅读原文
| 上一篇:2022-08-23_动物一“营业”,我就招架不住 | 下一篇:2025-03-04_DPO-Shift:一个参数可控改变DPO分布,缓解似然偏移 |
TAG标签: |
17 |
|
我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!
|
|
不达标就退款 高性价比建站 免费网站代备案 1对1原创设计服务 7×24小时售后支持 |
|
|
