Open nitroge opened 2 years ago
function loadScript(src) { // 创建一个 <script> 标签,并将其附加到页面 // 这将使得具有给定 src 的脚本开始加载,并在加载完成后运行 let script = document.createElement('script'); script.src = src; document.head.append(script); }
脚本加载是异步的,但我们希望了解脚本何时加载完成,以使用其中的新函数和变量。 让我们添加一个 callback 函数作为 loadScript 的第二个参数,该函数应在脚本加载完成时执行:
function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); }
这被称为“基于回调”的异步编程风格。异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用。
我们如何依次加载两个脚本:第一个,然后是第二个? 自然的解决方案是将第二个 loadScript 调用放入回调中,如下所示:
loadScript('/my/script.js', function(script) { alert(`Cool, the ${script.src} is loaded, let's load one more`); loadScript('/my/script2.js', function(script) { alert(`Cool, the second script is loaded`); }); });
乍一看,这是一种可行的异步编程方式。的确如此,对于一个或两个嵌套的调用看起来还不错。但对于一个接一个的多个异步行为,代码将会进入‘回调地狱’。 幸运的是,有其他方法可以避免。最好的方法之一就是 “promise”
Promise 对象的构造器(constructor)语法如下:
let promise = new Promise(function(resolve, reject) { // executor() });
传递给 new Promise 的函数被称为 executor。当 new Promise 被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。 它的参数 resolve 和 reject 是由 JavaScript 自身提供的回调。我们的代码仅在 executor 的内部。 由 new Promise 构造器返回的 promise 对象具有以下内部属性:
state — 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"。 result — 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。
executor 只能调用一个 resolve 或一个 reject。任何状态的更改都是最终的。 所有其他的再对 resolve 和 reject 的调用都会被忽略:
let promise = new Promise(function(resolve, reject) { resolve('done'); reject(new Error('…')); // 被忽略 setTimeout(() => resolve('…')); // 被忽略 });
Promise 对象的 state 和 result 属性都是内部的。我们无法直接访问它们。但我们可以对它们使用 .then/.catch/.finally 方法。
then
promise.then( function(result) { /* handle a successful result */ }, function(error) { /* handle an error */ }, );
.then 的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。 .then 的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
catch 如果我们只对 error 感兴趣,那么我们可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用 .catch(errorHandlingFunction) .catch(f) 调用是 .then(null, f) 的完全的模拟,它只是一个简写形式。
finally finally 总是在 promise 被 settled 时运行:即 promise 被 resolve 或 reject。 finally 是执行清理(cleanup)的很好的处理程序(handler),例如无论结果如何,都停止使用不再需要的加载指示符(indicator)。
new Promise((resolve, reject) => { /* 做一些需要时间的事儿,然后调用 resolve/reject */ }) // 在 promise 为 settled 时运行,无论成功与否 .finally(() => stop loading indicator) // 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止 .then(result => show result, err => show error)
new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // (*) }) .then(function(result) { // (**) alert(result); // 1 return result * 2; }) .then(function(result) { // (***) alert(result); // 2 return result * 2; });
.then(handler) 中所使用的处理程序(handler)可以创建并返回一个 promise。 在这种情况下,其他的处理程序(handler)将等待它 settled 后再获得其结果(result)。
new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }).then(function(result) { alert(result); // 1 return new Promise((resolve, reject) => { // (*) setTimeout(() => resolve(result * 2), 1000); }); })then(function(result) { alert(result); // 4 });
返回 promise 使我们能够构建异步行为链。
loadScript('/article/promise-chaining/one.js') .then(script => loadScript('/article/promise-chaining/two.js')) .then(script => loadScript('/article/promise-chaining/three.js')) .then(script => { // 脚本加载完成,我们可以在这儿使用脚本中声明的函数 one(); two(); three(); });
我们可以向链中添加更多的异步行为(action)。请注意,代码仍然是“扁平”的 — 它向下增长,而不是向右。这里没有“厄运金字塔”的迹象。 Thenables 确切地说,处理程序(handler)返回的不完全是一个 promise,而是返回的被称为 “thenable” 对象 — 一个具有方法 .then 的任意对象。它会被当做一个 promise 来对待。
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
这个特性允许我们将自定义的对象与 promise 链集成在一起,而不必继承自 Promise。
Promise 链在错误(error)处理中十分强大。当一个 promise 被 reject 时,控制权将移交至最近的 rejection 处理程序(handler)。
fetch('https://no-such-server.blabla') // rejects .then(response => response.json()) .catch(err => alert(err)); // TypeError: failed to fetch
.catch 不必是立即的。它可能在一个或多个 .then 之后出现。 隐式 try…catch Promise 的执行者(executor)和 promise 的处理程序(handler)周围有一个“隐式的 try..catch”。如果发生异常,它就会被捕获,并被视为 rejection 进行处理。
最后的 .catch 不仅会捕获显式的 rejection,还会捕获它上面的处理程序(handler)中意外出现的 error。 在任何情况下我们都应该有 unhandledrejection 事件处理程序(用于浏览器,以及其他环境的模拟),以跟踪未处理的 error 并告知用户(可能还有我们的服务器)有关信息,以使我们的应用程序永远不会“死掉”。
任务 你怎么看?.catch 会被触发么?
new Promise(function(resolve, reject) { setTimeout(() => { throw new Error('Whoops!'); }, 1000); }).catch(alert);
Promise.all 假设我们希望并行执行多个 promise,并等待所有 promise 都准备就绪。 例如,下面的 Promise.all 在 3 秒之后被 settled,然后它的结果就是一个 [1, 2, 3] 数组:
Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)), // 3 ]).then(alert);
请注意,结果数组中元素的顺序与其在源 promise 中的顺序相同。即使第一个 promise 花费了最长的时间才 resolve,但它仍是结果数组中的第一个。
如果任意一个 promise 被 reject,由 Promise.all 返回的 promise 就会立即 reject,并且带有的就是这个 error。
Promise.allSettled Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有:
{status:"fulfilled", value:result} 对于成功的响应 {status:"rejected", reason:error} 对于 error。
Promise.race 与 Promise.all 类似,但只等待第一个 settled 的 promise 并获取其结果(或 error)。
Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error('Whoops!')), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)), ]).then(alert); // 1
这里第一个 promise 最快,所以它变成了结果。第一个 settled 的 promise “赢得了比赛”之后,所有进一步的 result/error 都会被忽略。 Promise.resolve/reject Promise.resolve(value) 用结果 value 创建一个 resolved 的 promise。 如同:
let promise = new Promise(resolve => resolve(value));
Promise.reject Promise.reject(error) 用 error 创建一个 rejected 的 promise。 如同:
let promise = new Promise((resolve, reject) => reject(error));
一个题考察对 Promise 的掌握情况 问,以下代码输出什么?
Promise.resolve(x).then(y => console.log(x === y));
Promise 的处理程序(handlers).then、.catch 和 .finally 都是异步的。 即便一个 promise 立即被 resolve,.then、.catch 和 .finally 下面 的代码也会在这些处理程序(handler)之前被执行。
let promise = Promise.resolve(); promise.then(() => alert('promise done!')); alert('code finished'); // 这个 alert 先显示
队列(queue)是先进先出的:首先进入队列的任务会首先运行。 只有在 JavaScript 引擎中没有其它任务在运行时,才开始执行任务队列中的任务。
当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序(handler)就会被放入队列中:但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码,它会从队列中获取任务并执行它。
如果一个 promise 的 error 未被在微任务队列的末尾进行处理,则会出现“未处理的 rejection”。
let promise = Promise.reject(new Error('Promise Failed!')); // Promise Failed! window.addEventListener('unhandledrejection', event => alert(event.reason));
但是如果我们忘记添加 .catch,那么,微任务队列清空后,JavaScript 引擎会触发下面这事件:
如果我们迟一点再处理这个 error 会怎样?例如:
let promise = Promise.reject(new Error('Promise Failed!')); setTimeout(() => promise.catch(err => alert('caught')), 1000); // Error: Promise Failed! window.addEventListener('unhandledrejection', event => alert(event.reason));
为什么 unhandledrejection 处理程序(handler)会运行?我们已经捕获(catch)并处理了 error! 被添加到 setTimeout 中的 .catch 也会被触发。只是会在 unhandledrejection 事件出现之后才会被触发,所以它并没有改变什么(没有发挥作用)。
async 可以被放置在一个函数前面,即这个函数总是返回一个 promise。其他值将自动被包装在一个 resolved 的 promise 中。
async function f() { return 1; } f().then(alert); // 1
关键字 await 让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果,await 只在 async 函数中有效。
async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve('done!'), 1000); }); let result = await promise; // 等待,直到 promise resolve (*) alert(result); // "done!" } f();
await 实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。 相比于 promise.then,它只是获取 promise 的结果的一个更优雅的语法,同时也更易于读写。 await 接受 “thenables”
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();
Promise,async/await
简介:回调
脚本加载是异步的,但我们希望了解脚本何时加载完成,以使用其中的新函数和变量。
让我们添加一个 callback 函数作为 loadScript 的第二个参数,该函数应在脚本加载完成时执行:
这被称为“基于回调”的异步编程风格。异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用。
我们如何依次加载两个脚本:第一个,然后是第二个?
自然的解决方案是将第二个 loadScript 调用放入回调中,如下所示:
乍一看,这是一种可行的异步编程方式。的确如此,对于一个或两个嵌套的调用看起来还不错。但对于一个接一个的多个异步行为,代码将会进入‘回调地狱’。
幸运的是,有其他方法可以避免。最好的方法之一就是 “promise”
Promise
Promise 对象的构造器(constructor)语法如下:
传递给 new Promise 的函数被称为 executor。当 new Promise 被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。
它的参数 resolve 和 reject 是由 JavaScript 自身提供的回调。我们的代码仅在 executor 的内部。
由 new Promise 构造器返回的 promise 对象具有以下内部属性:
executor 只能调用一个 resolve 或一个 reject。任何状态的更改都是最终的。
所有其他的再对 resolve 和 reject 的调用都会被忽略:
消费函数:then,catch,finally
Promise 对象的 state 和 result 属性都是内部的。我们无法直接访问它们。但我们可以对它们使用 .then/.catch/.finally 方法。
then
.then 的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
.then 的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
catch
如果我们只对 error 感兴趣,那么我们可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用 .catch(errorHandlingFunction)
.catch(f) 调用是 .then(null, f) 的完全的模拟,它只是一个简写形式。
finally
finally 总是在 promise 被 settled 时运行:即 promise 被 resolve 或 reject。
finally 是执行清理(cleanup)的很好的处理程序(handler),例如无论结果如何,都停止使用不再需要的加载指示符(indicator)。
Promise 链
返回 promise
.then(handler) 中所使用的处理程序(handler)可以创建并返回一个 promise。
在这种情况下,其他的处理程序(handler)将等待它 settled 后再获得其结果(result)。
返回 promise 使我们能够构建异步行为链。
我们可以向链中添加更多的异步行为(action)。请注意,代码仍然是“扁平”的 — 它向下增长,而不是向右。这里没有“厄运金字塔”的迹象。 Thenables
确切地说,处理程序(handler)返回的不完全是一个 promise,而是返回的被称为 “thenable” 对象 — 一个具有方法 .then 的任意对象。它会被当做一个 promise 来对待。
这个特性允许我们将自定义的对象与 promise 链集成在一起,而不必继承自 Promise。
使用 promise 进行错误处理
Promise 链在错误(error)处理中十分强大。当一个 promise 被 reject 时,控制权将移交至最近的 rejection 处理程序(handler)。
.catch 不必是立即的。它可能在一个或多个 .then 之后出现。
隐式 try…catch
Promise 的执行者(executor)和 promise 的处理程序(handler)周围有一个“隐式的 try..catch”。如果发生异常,它就会被捕获,并被视为 rejection 进行处理。
任务
你怎么看?.catch 会被触发么?
Promise API
Promise.all
假设我们希望并行执行多个 promise,并等待所有 promise 都准备就绪。
例如,下面的 Promise.all 在 3 秒之后被 settled,然后它的结果就是一个 [1, 2, 3] 数组:
请注意,结果数组中元素的顺序与其在源 promise 中的顺序相同。即使第一个 promise 花费了最长的时间才 resolve,但它仍是结果数组中的第一个。
Promise.allSettled
Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有:
Promise.race
与 Promise.all 类似,但只等待第一个 settled 的 promise 并获取其结果(或 error)。
这里第一个 promise 最快,所以它变成了结果。第一个 settled 的 promise “赢得了比赛”之后,所有进一步的 result/error 都会被忽略。
Promise.resolve/reject
Promise.resolve(value) 用结果 value 创建一个 resolved 的 promise。
如同:
Promise.reject
Promise.reject(error) 用 error 创建一个 rejected 的 promise。
如同:
一个题考察对 Promise 的掌握情况
问,以下代码输出什么?
微任务(Microtask)
Promise 的处理程序(handlers).then、.catch 和 .finally 都是异步的。
即便一个 promise 立即被 resolve,.then、.catch 和 .finally 下面 的代码也会在这些处理程序(handler)之前被执行。
微任务队列(Microtask queue)
当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序(handler)就会被放入队列中:但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码,它会从队列中获取任务并执行它。
未处理的 rejection
如果一个 promise 的 error 未被在微任务队列的末尾进行处理,则会出现“未处理的 rejection”。
但是如果我们忘记添加 .catch,那么,微任务队列清空后,JavaScript 引擎会触发下面这事件:
如果我们迟一点再处理这个 error 会怎样?例如:
为什么 unhandledrejection 处理程序(handler)会运行?我们已经捕获(catch)并处理了 error!
被添加到 setTimeout 中的 .catch 也会被触发。只是会在 unhandledrejection 事件出现之后才会被触发,所以它并没有改变什么(没有发挥作用)。
Async/await
Async function
async 可以被放置在一个函数前面,即这个函数总是返回一个 promise。其他值将自动被包装在一个 resolved 的 promise 中。
Await
关键字 await 让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果,await 只在 async 函数中有效。
await 实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。
相比于 promise.then,它只是获取 promise 的结果的一个更优雅的语法,同时也更易于读写。 await 接受 “thenables”