为什么需要 Promise?
为什么我们需要 Promise, 最直接的答案就是它更直观、好用。Promise 出现之前,我们使用事件和回调。我们创建并加载一个脚本文件, 监听 onload
事件, 执行对应的回调函数。如下所示:
const loadScript = (src, callback) => {
// 创建一个 <script> 标签, 并将其添加到页面
let script = document.createElement("script");
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
};
loadScript(
"https://unpkg.com/vue@3/dist/vue.global.js",
function (error, script) {
if (error) {
console.log(error);
} else {
console.log("loading Vue"); // "loading Vue"
}
}
);
此处只加载一个脚本文件,回调看起来还比较清晰,但是如果我们想加载多个脚本文件呢?如下所示:
loadScript(
"https://unpkg.com/vue@3/dist/vue.global.js",
function (error, script) {
console.log("loading Vue"); // "loading Vue"
loadScript(
"https://unpkg.com/react@18/umd/react.development.js",
function (error, script) {
console.log("loading React"); // "loading React"
}
);
}
);
在上面的代码中:我们加载 Vue, 如果没有发生错误,我们加载 React......
随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,这就被称为“回调地狱”。此时 Promise 闪亮登场了,它可以很好地解决回调地狱问题,且代码简单易读。
如果改成 Promise:
const loadScript = (src) => {
return new Promise(function (resolve, reject) {
let script = document.createElement("script");
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script);
});
};
loadScript("https://unpkg.com/vue@3/dist/vue.global.js")
.then((script) => {
console.log("loading Vue"); // "loading Vue"
return loadScript("https://unpkg.com/react@18/umd/react.development.js");
})
.then((script) => console.log("loading React")) // "loading React"
.catch((error) => console.log(error));
Promise 介绍
Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。— MDN
它的构造器语法如下:
let promise = new Promise(function(resolve, reject) {
// executor
})
executor
会自动运行并尝试执行。尝试结束后,如果成功则调用 resolve
,如果出现 error
则调用 reject
。
所以,executor
最终将 Promise
移至以下状态之一:
由图可知,new Promise
构造器返回的 pending
对象具有以下内部属性:
state
:初始值是 pending
,resolve
调用后变为 fulfilled
, reject
调用之后变为 rejected
result
:初始值是 undefined
,resolve(value)
调用后变为 value
, reject(error)
调用时变为 error
Promise 支持链式调用,.then
、.catch
和 .finally
方法均支持链式调用。其中,.then
方法接收两个参数,第一个参数处理已决议状态(fulfilled
)的回调函数,第二个参数则处理已经拒绝(rejected
)的回调函数。每一个 .then()
方法返回的是一个新生成的 Promise 对象,这个对象可被用作链式调用。如:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
模拟实现 Promise
我们的第一版 Promise 具有以下功能:
Promise
resolve
或 reject
一个 Promise
,仅可敲定一次.then()
方法接收回调函数,满足无论 Promise 的状态是否已经敲定,都需要单独处理回调函数.then()
方法暂不支持链式调用,我们将在第二版实现这个功能MyPromise
是一个类,它有下面三个原型方法:
MyPromise.prototype.resolve(value)
MyPromise.prototype.reject(reason)
MyPromise.prototype.then(onFulfilled, onRejected)
希望可以如下使用我们实现的 Promise
,也可作为我们稍后的测试代码:
// .resolve() 先于 .then() 执行, 无论它们代码位置的先后
// .resolve() 在 .then() 前
const tp1 = new MyPromise();
tp1.resolve("abc");
tp1.then((value) => {
console.log(value === "abc"); // true
});
// .then() 在 .resolve() 前
const tp2 = new MyPromise();
tp2.then((value) => {
console.log(value === "def"); // true
});
tp2.resolve("def");
class MyPromise {
// 处理 .then 中状态是 pending 的所有任务,加入任务数组中
_fulfillmentTasks = [];
_rejectionTasks = [];
_preomiseResult = undefined;
_promiseState = "pending";
then(onFulfilled, onRejected) {
const fulfillmentTask = () => {
if (typeof onFulfilled === "function") {
onFulfilled(this._preomiseResult);
}
};
const rejectionTask = () => {
if (typeof onRejected === "function") {
onRejected(this._preomiseResult);
}
};
switch (this._promiseState) {
case "pending":
this._fulfillmentTasks.push(fulfillmentTask);
this._rejectionTasks.push(rejectionTask);
case "fulfilled":
addToTaskQueue(fulfillmentTask);
break;
case "rejected":
addToTaskQueue(rejectionTask);
default:
throw new Error();
}
}
resolve(value) {
if (this._promiseState !== "pending") return this;
this._promiseState = "fulfilled";
this._preomiseResult = value;
this._clearAndEnqueueTask(this._fulfillmentTasks);
return this; // 可以链式调用
}
reject(error) {
if (this._promiseState !== "pending") return this;
this._promiseState = "rejected";
thsi._promiseResult = error;
this._clearAndEnqueueTask(this._rejectionTasks);
return this; // 可以链式调用
}
_clearAndEnqueueTask(tasks) {
this._fulfillmentTasks = undefined;
this._rejectionTasks = undefined;
tasks.map(addToTaskQueue);
}
}
function addToTaskQueue(task) {
setTimeout(task, 0);
}
resolve()
resolve()
方法的工作原理如下:
fulfilled
)或已拒绝(rejected
),什么都不做。因为一个 Promise 仅可以被敲定一次。pending
变为 fulfilled
;并将结果缓存到 this._promiseResult
中。 setTimeout()
模拟异步执行。reject()
原理同 resolve()
,只不过它处理的是 Promise 的 rejected
情况。
then()
then()
方法需要处理下面两种情况:
pending
状态,需要把它封装成待处理的函数: onFulfilled
和 onRejected
,添加到对应的任务数组中(_fulfillmentTasks
和 _rejectionTasks
),它们将在 Promise 已经敲定后再执行。onFulfilled
、onRejcted
可以直接触发。Promise 必须始终是异步敲定,这就是我们为什么不直接执行 task
,而是添加到事件循环(event loop)的任务队列中。真正的 Promise 不是使用 setTimeout
模拟的,它使用的是微任务(microtasks)。
我们将实现链式调用,先看一段代码:
new MyPromise()
.resolve("result1")
.then((x) => {
console.log(x === "result1"); // true
return "result2";
})
.then((x) => {
console.log(x === "result2"); // true
return new Error("error1");
})
.then((x) => {
console.log(x.message === "error1"); // true
});
在上面的例子中:
.then()
方法在传递了一个箭头函数,返回一个值 result2
.then()
方法在中接收了 result2
.then()
方法如何实现链式调用呢?
.then()
返回一个 Promise,无论前一个 Promise 返回的是 onFullfilled
还是 onRejected
,它都接收其状态。onFulfilled
或 onRejected
都缺失,都通过 .then()
方法返回给该 Promise。下面的图更为直观:
因此,我们将 .then()
方法改进如下:
then(onFulfilled, onRejcted) {
const resultPromise = new MyPromise(); // new
const fulfillmentTask = () => {
if (typeof onFulfilled === "function") {
const returned = onFulfilled(this._preomiseResult);
resultPromise.resolve(returned);
} else {
resultPromise.resolve(this._promiseResult); // new
}
};
const rejectionTask = () => {
if (typeof onRejected === "function") {
const returned = onRejected(this._promiseResult);
resultPromise.resolve(returned); // new
} else {
resultPromise.reject(this._promiseResult);
}
};
// ...
return resultPromise; // new
}
.then()
返回一个新的 Promise,此外,我们需要关注:
fulfillmentTask
会有两种不同表现:如果提供了 onFullfilled
函数,调用它并将其结果传递给 resultPromise
的 resolve
方法。
如果没提供,使用当前 Promise 的敲定结果 this._promiseResult
传递给 resultPromise
的 resolve 方法。
rejectionTask
也有两种表现:如果提供了 onRejected
函数,调用它并将其结果传递给 resultPromise
的 resolve
方法。注意:resultPromise
并没有被拒绝,我们将在后文详细解释。
如果没提供,使用当前 Promise 的敲定结果 this._promiseResult
传递给 resultPromise
的 resolve
方法。
thenable 是一个对象,它具有方法 .then
,被当作一个 Promise 对待。.then()
方法中还可以创建并返回一个 Promise,在这种情况下,其他的处理程序将等待它被敲定后再获得其结果。
asyncFunc1()
.then((result) => {
console.log(result1 === "Result of asyncFun1()"); // true
return asyncFunc2(); // A
})
.then((result2Promise) => {
result2Promise
.then((result2) => { // B
console.log(result2 === "Result of asyncFun2"); // true
})
})
在 A 处,如果返回了一个 Promise,然后在第二个 .then()
中作为参数传入,并敲定了这个 Promise。官方的 Promise 可以直接敲定嵌套的 Promise,如下面的代码:
asyncFunc1()
.then((result) => {
console.log(result1 === "Result of asyncFun1()"); // true
return asyncFunc2(); // A
})
.then((result) => {
// result2 是一个已兑现的结果,而非 Promise
console.log(result2 === "Result of asyncFun2"); // true
})
我们将这样处理 then()
方法中返回的 thenable,如下图:
我们可以这样判断 thenable 对象:
function isThenable(value) {
return (
typeof value === "object" &&
value !== null &&
typeof value.then === "function"
);
}
我们再来完善 .resolve()
方法:
resolve(value) {
if (this._alreadyResolved) return this;
this._alreadyResolved = true;
if (isThenable(value)) {
value.then(
(result) => this._doFulfill(result),
(error) => this._doReject(error);
)
} else {
this._doFulfill(value);
}
return this;
}
_doFulfill(value) { // [new]
console.log(!isThenable(value)) // true
this._promiseState = "fulfilled";
this._promiseResult = value;
this._clearAndEnqueueTasks(this._fulfillmentTasks);
}
this._alreadyResolved
是为了锁定 Promise 的状态的。返回一个已敲定状态的 Promise,那么 then()
返回的 Promise 的状态也会是已敲定。返回已拒绝状态同理。
我们希望错误也能够得到正确处理:
new MyPromise()
.resolve("a")
.then((value) => {
console.log(value === "a"); // true
throw "b"; // 抛出一个错误
})
.catch((error) => {
console.log(error === "b"); // true
})
如果我们要处理错误,只需要在 .then() 方法中添加相关代码即可:
const fulfillmentTask = () => {
if (typeof onFulfilled === "function") {
this._runReactionSafely(resultPromise, onFulfilled); // [new]
} else {
// `onFulfilled` 没有传
// => 我们需要传已敲定的结果
resultPromise.resolve(this._promiseResult);
}
};
_runReactionSafely(resultPromise, reaction) { // [new]
try {
const returned = reaction(this._promiseResult);
resultPromise.resolve(returned);
} catch (e) {
resultPromise.reject(e);
}
}
错误处理过程如图所示:
总结
至此,我们模拟实现了 Promise,包含 .then()
、.resolve()
、 .reject()
方法的实现。
class MyPromise {
_fulfillmentTasks = [];
_rejectionTasks = [];
_promiseResult = undefined;
_promiseState = "pending";
_alreadyResolved = false;
then(onFulfilled, onRejected) {
const resultPromise = new MyPromise();
const fulfillmentTask = () => {
if (typeof onFulfilled === "function") {
this._runReactionSafely(resultPromise, onFulfilled);
} else {
resultPromise.resolve(this._promiseResult);
}
};
const rejectionTask = () => {
if (typeof onRejected === "function") {
this._runReactionSafely(resultPromise, onRejected);
} else {
resultPromise.reject(this._promiseResult);
}
};
switch (this._promiseState) {
case "pending":
this._fulfillmentTasks.push(fulfillmentTask);
this._rejectionTasks.push(rejectionTask);
break;
case "fulfilled":
addToTaskQueue(fulfillmentTask);
break;
case "rejected":
addToTaskQueue(rejectionTask);
break;
default:
throw new Error();
}
return resultPromise;
}
catch(onRejected) {
return this.then(null, onRejected);
}
_runReactionSafely(resultPromise, reaction) {
try {
const returned = reaction(this._promiseResult);
resultPromise.resolve(returned);
} catch (e) {
resultPromise.reject(e);
}
}
resolve(value) {
if (this._alreadyResolved) return this;
this._alreadyResolved = true;
if (isThenable(value)) {
value.then(
(result) => this._doFulfill(result),
(error) => this._doReject(error)
);
} else {
this._doFulfill(value);
}
return this;
}
_doFulfill(value) {
this._promiseState = "fulfilled";
this._promiseResult = value;
this._clearAndEnqueueTasks(this._fulfillmentTasks);
}
reject(error) {
if (this._alreadyResolved) return this;
this._alreadyResolved = true;
this._doReject(error);
return this;
}
_doReject(error) {
this._promiseState = "rejected";
this._promiseResult = error;
this._clearAndEnqueueTasks(this._rejectionTasks);
}
_clearAndEnqueueTasks(tasks) {
this._fulfillmentTasks = undefined;
this._rejectionTasks = undefined;
tasks.map(addToTaskQueue);
}
}
function isThenable(value) {
return (
typeof value === "object" &&
value !== null &&
typeof value.then === "function"
);
}
function addToTaskQueue(task) {
setTimeout(task, 0);
}
小练习
如何使用 Promise 封装 XMLHttpRequest?
答案:参考如下
function load(url) {
return new Promise(function (resolve, reject) {
const request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status == 200) {
resolve(this.response);
} else {
reject(this.status);
}
};
request.open("GET", url, true);
request.send();
});
}
// test
load("testUrl")
.then((response) => {
const result = JSON.parse(response);
// ...
})
.catch((error) => {
// ...
});
阅读量:2021
点赞量:0
收藏量:0