本文章部分参考Promise,添加了自己的理解,让读者更好快速理解promise
Promise简介
什么是promise,这里使用类比的方法解释:
你走进汉堡店,买了一个汉堡,过了一会儿,你收到了你的汉堡
// 传统回调方式
function orderBurger(callback) {
console.log('下单汉堡...');
setTimeout(() => { callback('🍔 你的汉堡'); }, 3000);
}
orderBurger((burger) => { console.log('拿到了:', burger); });
看起来很简单,但是如果情况变得复杂起来呢?
你拿到了你的汉堡,此时你决定再点一份薯条,收到薯条以后,你又决定再点一杯可乐。 在传统的回调方式中,你需要在下单汉堡成功的情况里面嵌套下单薯条的两种情况,再在下单薯条成功的情况里面嵌套下单可乐的两种情况,就像这样:
function orderBurger(callback) {
console.log("🍔 正在制作汉堡...");
setTimeout(() => {
callback(null, "🍔 你的汉堡");
}, 2000);
}
function orderFries(callback) {
console.log("🍟 正在炸薯条...");
setTimeout(() => {
callback(null, "🍟 你的薯条");
}, 1500);
}
function orderCoke(callback) {
console.log("🥤 正在倒可乐...");
setTimeout(() => {
callback(null, "🥤 你的可乐");
}, 1000);
}
// 使用回调(越来越深的嵌套)
console.log("👋 欢迎光临!开始点餐...\n");
orderBurger((error, burger) => {
if (error) {
console.log("❌ 汉堡出错:", error);
return;
}
console.log("✅ 拿到了:", burger);
console.log("");
// 第一层嵌套
orderFries((error, fries) => {
if (error) {
console.log("❌ 薯条出错:", error);
return;
}
console.log("✅ 拿到了:", fries);
console.log("");
// 第二层嵌套 - 回调地狱开始!
orderCoke((error, coke) => {
if (error) {
console.log("❌ 可乐出错:", error);
return;
}
console.log("✅ 拿到了:", coke);
console.log("");
console.log("🎉 所有食物都拿到了,开始享用!");
// 如果还想点甜品... 继续嵌套 😱
});
});
});
随着情况越来越复杂,嵌套回越来越深,形成回调地狱。
而使用promise,就会变成这种扁平的结构,方便修改和debug。
// 1️⃣ 点汉堡
function orderBurger() {
console.log("🍔 正在制作汉堡...");
return new Promise((resolve) => {
setTimeout(() => {
resolve("🍔 你的汉堡");
}, 2000);
});
}
// 2️⃣ 点薯条
function orderFries() {
console.log("🍟 正在炸薯条...");
return new Promise((resolve) => {
setTimeout(() => {
resolve("🍟 你的薯条");
}, 1500);
});
}
// 3️⃣ 点可乐
function orderCoke() {
console.log("🥤 正在倒可乐...");
return new Promise((resolve) => {
setTimeout(() => {
resolve("🥤 你的可乐");
}, 1000);
});
}
console.log("👋 欢迎光临!开始点餐...\n");
// 方式1: Promise 链式调用(推荐)
orderBurger()
.then((burger) => {
console.log("✅ 拿到了:", burger);
console.log("");
return orderFries(); // 返回新的 Promise
})
.then((fries) => {
console.log("✅ 拿到了:", fries);
console.log("");
return orderCoke(); // 返回新的 Promise
})
.then((coke) => {
console.log("✅ 拿到了:", coke);
console.log("");
console.log("🎉 所有食物都拿到了,开始享用!");
})
.catch((error) => {
console.log("❌ 出错了:", error);
});
好了,我们系统地解读一下promise的语法:promise分成两个部分,生产者代码和消费者代码。
- “生产者代码(producing code)”会做一些事儿,并且会需要一些时间。例如,通过网络fetch数据。
- “消费者代码(consuming code)”想要在“生产者代码”完成工作的第一时间就能获得其工作成果。许多函数可能都需要这个结果。
Promise 是将“生产者代码”和“消费者代码”连接在一起的一个特殊的 JavaScript 对象。
生产者
Promise 对象的构造器(constructor)语法如下:
let promise = new Promise((resolve, reject)=> {
// 执行生产者代码
});
传递给 new Promise 的函数被称为 executor。当 new Promise 被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。
它的参数 resolve 和 reject 是由 JavaScript 自身提供的回调。我们的代码仅在 executor 的内部。可以理解为执行成功的消息如何传递给处理成功的消费者 以及 执行失败后的报错如何传给处理报错的消费者 都是已经被js处理好了,我们这里只管执行(以及报告结果)。
new Promise((resolve, reject) => {
// ← 生产者区域(你的代码)
// 你在这里生产结果
if (成功) {
resolve(结果); // 把结果交给 JS 引擎
} else {
reject(错误); // 把错误交给 JS 引擎
}
})
// ← JS 引擎的工作区(自动处理)
// JS 会自动把 resolve 的结果传给 .then
// JS 会自动把 reject 的错误传给 .catch
.then((结果) => {
// ← 消费者区域(你的代码)
// 你在这里使用结果
})
.catch((错误) => {
// ← 消费者区域(你的代码)
// 你在这里处理错误
});
executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve,如果出现 error 则调用 reject。
由 new Promise 构造器返回的 promise 对象具有以下内部属性:
state—— 最初是"pending",然后在resolve被调用时变为"fulfilled",或者在reject被调用时变为"rejected"。result—— 最初是undefined,然后在resolve(value)被调用时变为value,或者在reject(error)被调用时变为error。
所以,executor 最终将 promise 移至以下状态之一:
下面是一个 promise 构造器和一个简单的 executor 函数,该 executor 函数具有包含时间(即 setTimeout)的“生产者代码”:
let promise = new Promise(function(resolve, reject) {
// 当 promise 被构造完成时,自动执行此函数
// 1 秒后发出工作已经被完成的信号,并带有结果 "done"
setTimeout(() => resolve ("done") , 1000);
});
通过运行上面的代码,我们可以看到两件事儿:
-
executor 被自动且立即调用(通过
new Promise)。 -
executor 接受两个参数:
resolve和reject。这些函数由 JavaScript 引擎预先定义,因此我们不需要创建它们。我们只需要在准备好(译注:指的是 executor 准备好)时调用其中之一即可。经过 1 秒的“处理”后,executor 调用
resolve("done")来产生结果。这将改变promise对象的状态:
这是一个成功完成任务的例子,一个“成功实现了的诺言”。
下面则是一个 executor 以 error 拒绝 promise 的示例:
let promise = new Promise(function(resolve, reject) {
// 1 秒后发出工作已经被完成的信号,并带有 error
setTimeout(() => reject (new Error("Whoops!")) , 1000);
});
对 reject(...) 的调用将 promise 对象的状态移至 "rejected":
总而言之,executor 应该执行一项工作(通常是需要花费一些时间的事儿),然后调用 resolve 或 reject 来改变对应的 promise 对象的状态。
与最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。
- 只能有一个结果
executor 任何状态的更改都是不可再次改变的
所有其他的再对 resolve 和 reject 的调用都会被忽略:
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // 被忽略
setTimeout(() => resolve("…")); // 被忽略
});
- 以
Error对象 reject
如果什么东西出了问题,executor 应该调用 reject。这可以使用任何类型的参数来完成(就像 resolve 一样)。但建议使用 Error 对象(或继承自 Error 的对象)。
- resolve/reject 可以立即进行
实际上,executor 通常是异步执行某些操作,并在一段时间后调用 resolve/reject,但这不是必须的。我们还可以立即调用 resolve 或 reject,就像这样:
let promise = new Promise(function(resolve, reject) {
// 不花时间去做这项工作
resolve(123); // 立即给出结果:123
});
例如,当我们开始做一个任务,随后发现一切都已经完成并已被缓存时,可能就会发生这种情况。
这挺好。我们立即就有了一个 resolved 的 promise。
state和result都是内部的
Promise 对象的 state 和 result 属性都是内部的。我们无法直接访问它们。但我们可以对它们使用 .then/.catch/.finally 方法。我们在下面对这些方法进行了描述。
消费者:then,catch
消费者可以通过使用 .then 和 .catch 方法注册消费函数的结果。
[then]处理成功结果,[catch]处理失败结果,很好理解吧
清理者:finally(没有这个名词,我自己起的)
finally 的功能是设置一个处理程序在前面的操作完成后,执行清理/终结。
例如,停止加载指示器,关闭不再需要的连接等。
把它想象成派对的终结者。无论派对是好是坏,有多少朋友参加,我们都需要(或者至少应该)在它之后进行清理。
new Promise((resolve, reject) => {
/* 做一些需要时间的事,之后调用可能会 resolve 也可能会 reject */
})
// 在 promise 为 settled 时运行,无论成功与否
.finally(() => stop loading indicator)
// 所以,加载指示器(loading indicator)始终会在我们继续之前停止
.then(result => show result, err => show error)
注意
-
finally处理程序(handler)没有参数。在finally中,我们不知道 promise 是否成功。没关系,因为我们的任务通常是执行“常规”的完成程序(finalizing procedures)。 -
finally不消费结果,它只会将结果“传递”给下一个合适的处理程序。例如,在这结果被从
finally传递给了then:new Promise((resolve, reject) => { setTimeout(() => resolve("value"), 2000) }) .finally(() => alert("Promise ready")) // 先触发 .then(result => alert(result)); // <-- .then 显示 "value"正如我们所看到的,第一个 promise 返回的
value通过finally没被消费,被传递给了下一个then。