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