Promise 对象的构造器
Promise 对象的构造器语法如下:
let promise = new Promise(function (resolve, reject) {
console.log("执行 1"); // 生产者代码,想象成一个“歌手”
})
传递给 new Promise
的函数被称为 executor
。
当 new Promise
被创建,executor
会自动运行。它包含最终应产出结果的生产者代码。按照上面的类比:executor
就是“歌手”。executor
的参数 resolve
和 reject
是由 JavaScript 自身提供的回调函数。我们的代码仅在 executor 的内部。
当 executor 获得了结果,无论是早还是晚都没关系,它应该调用以下两个回调之一:
resolve(value)
任务成功,value 是执行任务得到的结果reject(error)
任务出错,error 是代表错误的对象
总结一下,executor 会自动执行工作任务。任务结束后,成功则调用 resolve,失败调用 reject。
承诺的状态变化
构造器 new Promise
返回的 promise对象实例
有以下两个重要的 内部属性
:
state
: 最初是“pending”,当 resolve 被调用时,是“fulfilled”。当 reject 被调用时,是“rejected”result
:最初是“undefined”,当resolve(value)
被调用时其值是value
。当reject(error)
被调用时,值是“error”。其他方法无法修改 result 值
总结,executor 最终将 promise 变为以下状态之一:
代码举例
一个 promise 构造器和一个简单的 executor 函数:
console.log("主线程 1");
let promise = new Promise(function (resolve, reject) {
// 当 promise 被构造完成时,自动执行此函数
console.log("executor 被立即调用");
// 1000ms 后发出任务完成信号, 成功结果 value 为 "成功完成"
setTimeout(() => {
resolve("成功完成");
}, 1000)
})
console.log("主线程 2");
// 主线程 1
// executor 被立即调用
// 主线程 2
我们可以看出两件事儿:
- executor 被立即调用(同步执行)
- resolve 和 reject 两个函数由 JavaScipt 引擎预先定义。我们只需要在合适的时机去调用其中之一即可。
经过 1 秒的 “处理” 后,executor 调用 resolve(“done”) 来产生结果。这将改变 promise 对象的状态:
这是一个成功完成任务的例子,一个“成功实现了的诺言”。
再来一个 executor 以 error 拒绝承诺的例子:
console.log("主线程 1");
let promise = new Promise(function (resolve, reject) {
console.log("执行了");
setTimeout(() => {
reject(new Error("出错了!"));
}, 1000);
})
console.log("主线程 1");
输出信息如下:
promise 对象的状态:
executor 执行的一般是异步任务,然后调用 resolve 或者 reject 来改变对应的 promise 对象的状态。
一个调用了 resolved 或 rejected 的 promise 都会被称为 “settled”的 promise。
细节
一些细节问题。
状态可以反复横跳吗?
executor 只能调用一个 resolve 或一个 reject 。任何状态的更改都是最终的。
调用之后所有其他的再对 resolve 和 reject 的调用都会被忽略!
例如:
console.log("主线程 1");
let promise = new Promise(function (resolve, reject) {
console.log("执行");
setTimeout(() => {
resolve("任务成功");
console.log("继续执行 1");
resolve("再次成功");// 忽略
console.log("继续执行 2");
}, 1000);
setTimeout(() => {
reject(new Error("任务失败!"));// 忽略
console.log("继续执行 3");
reject(new Error("再次失败!"));// 忽略
console.log("继续执行 4");
}, 1000);
});
console.log("主线程 2");
// 主线程 1
// 执行
// 主线程 2
// 继续执行 1
// 继续执行 2
// 继续执行 3
// 继续执行 4
宗旨是,一个被 executor 完成的工作只能有一个 resolve 或一个 error。
并且, resolve/reject 只需要一个参数(或不包含任何参数),并且将忽略额外的参数.
reject 参数必须是 Error 对象?
可以使用任何类型的参数来完成(就像 resolve 一样)。
但是建议使用 Error 对象(或继承自 Error 的对象)。这样做的理由很快就会显而易见。
resolve 和 reject 可以立即执行
executor 通常是异步执行某些操作,并在一段时间后调用 resolve/reject ,但这不是必须的。
我们还可以立即调用 resolve 或 reject ,就像这样:
let promise = new Promise(function(resolve, reject) {
// 不花时间去做这项工作
resolve(123); // 立即给出结果:123
});
state 和 result 都是内部的
我们无法直接访问它们。但我们可以对它们使用 .then / .catch / .finally 方法
resolve、reject 会导致 executor 函数返回吗
不会。问这个问题看来你还是没真正懂。resolve 与 reject 都是 JavaScript 负责调用的,不会直接导致 executor 函数返回。就算那种没有异步任务,立即执行的 resolve,其后面的代码也会继续执行。
console.log(1);
let promise = new Promise(function (resolve, reject) {
resolve('DDOONNEE');
console.log("3");
});
console.log(2);
打印
1
3
2
消费者:then,catch,finally
Promise 对象充当的是 executor(“生产者代码”或“歌手”)和消费函数(“粉丝”)之间的桥梁,消费函数将接收结果或 error。
消费函数一般是:then,catch,finally
then
最重要最基础的一个消费函数
语法:
promise 对象. then(
function(result) { /* 处理成功后的结果 */ },
function(error) { /* 处理错误 */ }
);
- 第一个参数是一个函数,该函数将在 promise resolved 后运行并接收 result
- 第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
示例:
console.log("主线程 1");
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("任务成功");
}, 1000);
})
console.log("主线程 2");
promise.then(
function (result) {
// 处理成功的结果
console.log(result);
},
function (error) {
// 处理错误
console.log(error);
}
)
console.log("主线程 3");
// 主线程 1
// 主线程 2
// 主线程 3
// 任务成功
可以看出,then 在任务执行结束后被执行,所以也是异步的。
第一个参数函数被执行,在 reject 的情况下,运行第二个:
主线程 1
主线程 2
主线程 3
Error: 出错了!
at Timeout._onTimeout (E:\My-FrontEND-Way \ 现
代 JSinfo\JS 篇 \ Promise\1 - 消费者 1-then\0 - 成功时消费
.js:5:16)
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
如果我们只对成功的情况感兴趣,那么我们可以只为 .then 提供一个函数参数:
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1 秒后显示 "done!"
如果我们只对 error 感兴趣,那么我们可以使用 null 作为第一个参数:
.then(null,errorHandlingFunction) 。
then 可以写多个,并且状态改变时,都会被调用
let p = Promise.resolve("OKKKK")
p.then(value => {
console.log("then 1", value);
})
p.then(value => {
console.log("then 2", value);
})
输出:
then 1 OKKKK
then 2 OKKKK
catch
如果我们只对 error 感兴趣, 也可以使用 .catch(errorHandlingFunction)
.catch(f)
调用是 .then(null, f)
的完全的模拟,它只是一个简写形式。
finally
.finally(f) 调用与 .then(f, f) 类似,在某种意义上, f 总是在 promise 被 settled 时运行:即 promise 被 resolve 或 reject 之后。
finally 是执行清理(cleanup)的很好的处理程序(handler)
console.log("主线程 1");
let promise = new Promise(function (resolve, reject) {
console.log("任务 1 要执行了");
setTimeout(() => {
resolve("我是任务 1 成功执行后的结果");
// reject("出错了");
}, 1000);
})
console.log("主线程 2");
promise.finally(function () {
console.log("清理任务 1 占用的系统资源");
}).then(
function (result) {
// 继续处理上一个 promise 的结果
console.log("pormise 1 的 then 的任务成功处理函数:" + result);
console.log("pormise 1 的 then 的任务成功处理函数处理完毕");
},
function (error) {
})
console.log("主线程 3");
执行结果:
主线程 1
任务 1 要执行了
主线程 2
主线程 3
清理任务 1 的资源
pormise 1 的 then 的任务成功处理函数: 我是任务 1 成功 成功执行后的结果
pormise 1 的 then 的任务成功处理函数处理完毕
finally(f) 其实并不是 then(f,f) 的别名。它们之间有一些细微的区别 /
- finally 的 f 函数没有参数。在 finally 内,我们不知道 promise 是成功还是失败,只知道 promise 被 settled 了
- finally 将 resolve 接收到的 value 或者 reject 接收到的 error 传递给下一个消费者。
finally 目的并不是处理 promise 的结果。所以将 promise 结果传递给了后面的消费者。
实际代码
接下来,让我们看一下关于 promise 如何帮助我们编写异步代码的。
用于加载脚本的 loadScript 函数,基于回调:
function loadScript(src, callback) {
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);
}
让我们用 promise 重写它。
新函数 loadScript 将不需要 callback 函数。取而代之的是,它将创建并返回一个在加载完成时解析(resolve)的 promise 对象。外部代码可以使用 .then 向其添加处理程序(订阅函数):
function 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);
});
}
之后,可以调用 then:
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('Another handler...'));
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。