反教条主义,通俗和理解战胜一切。概念是为实用服务的。现有想法后有概念,切莫本末倒置!
JS异步
注: 只谈出现的原因、区别与总结。细节此处不予赘述 。理解这些异步方法之前一定要理解事件循环机制,如果不了解请看相应文章先把事件循环搞明白噢~
原始方法——回调
由于JS基于事件循环,经常存在异步的概念。
JavaScript 主机(host)环境提供了许多函数,这些函数允许我们计划 异步 行为。换句话说,我们现在开始执行的行为,但它们会在稍后完成。
例如,setTimeout 函数就是一个这样的函数。
回调地狱/厄运金字塔
对于一个接一个的多个异步行为,代码将会变成这样:
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...加载完所有脚本后继续 (*)
}
});
}
});
}
});
为战胜回调地狱而诞生——Promise
promise(承诺)顾名思义…
本质上其实是生产者消费者模型或订阅模型
这种类比并不十分准确,因为 JavaScipt 的 promise 比简单的订阅列表更加复杂:它们还拥有其他的功能和局限性。但以此开始挺好的。
Promise构造器
let promise = new Promise(function(resolve, reject) {
// executor(生产者代码,“歌手”)
});
以setTimeout为例
let promise = new Promise(function(resolve, reject) {
// 当 promise 被构造完成时,自动执行此函数
// 1 秒后发出工作已经被完成的信号,并带有结果 "done"
setTimeout(() => resolve("done"), 1000);
});
let promise = new Promise(function(resolve, reject) {
// 1 秒后发出工作已经被完成的信号,并带有 error
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
Promise 则更加灵活。我们可以随时添加处理程序(handler):如果结果已经在了,它们就会执行。
以setTimeout为例
let promise = new Promise(function(resolve, reject) {
// 当 promise 被构造完成时,自动执行此函数
// 1 秒后发出工作已经被完成的信号,并带有结果 "done"
setTimeout(() => resolve("done"), 1000);
});
.then接受“thenables”
确切地说,处理程序(handler)返回的不完全是一个 promise,而是返回的被称为 “thenable” 对象 — 一个具有方法 .then 的任意对象。它会被当做一个 promise 来对待。
这个想法是,第三方库可以实现自己的“promise 兼容(promise-compatible)”对象。它们可以具有扩展的方法集,但也与原生的 promise 兼容,因为它们实现了 .then 方法。
这是一个 thenable 对象的示例:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// 1 秒后使用 this.num*2 进行 resolve
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // 1000ms 后显示 2
JavaScript 检查在 (*) 行中由 .then 处理程序(handler)返回的对象:如果它具有名为 then 的可调用方法,那么它将调用该方法并提供原生的函数 resolve 和 reject 作为参数(类似于 executor),并等待直到其中一个函数被调用。在上面的示例中,resolve(2) 在 1 秒后被调用 (**)。然后,result 会被进一步沿着链向下传递。
这个特性允许我们将自定义的对象与 promise 链集成在一起,而不必继承自 Promise。
Async/await
Async/await 本质上是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。
本质上返回的还是Promise
以setTimeOut为例子
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待,直到 promise resolve (*)
alert(result); // "done!"
}
f();
以上代码用Promise包裹setTimeout返回Promise对象
await 接受 “thenables”
像 promise.then 那样,await 允许我们使用 thenable 对象(那些具有可调用的 then 方法的对象)。这里的想法是,第三方对象可能不是一个 promise,但却是 promise 兼容的:如果这些对象支持 .then,那么就可以对它们使用 await。
这有一个用于演示的 Thenable 类,下面的 await 接受了该类的实例:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve);
// 1000ms 后使用 this.num*2 进行 resolve
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
}
async function f() {
// 等待 1 秒,之后 result 变为 2
let result = await new Thenable(1);
alert(result);
}
f();
如果 await 接收了一个非 promise 的但是提供了 .then 方法的对象,它就会调用这个 .then 方法,并将内建的函数 resolve 和 reject 作为参数传入(就像它对待一个常规的 Promise executor 时一样)。然后 await 等待直到这两个函数中的某个被调用(在上面这个例子中发生在 (*) 行),然后使用得到的结果继续执行后续任务。
用例对比
示例: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);
}
Resolve/reject 可以立即进行
关于这个知识点补充一点
🌰
let promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
})
promise.then(() => console.log(3))
console.log(2)
上面代码在控制台打印的结果是 1, 2, 3
因为: 在 new Promise()的时候,Promise的执行器就会立马执行,但是调用resolve()会触发异步操作,传入的then()方法的函数会被添加到任务队列并异步执行
参考资料
《现代 JavaScript 教程》