回调
“基于回调”的异步编程风格: 异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用
function loadScript(src, callback) {let script = document.createElement("script");script.src = src;script.onload = () => callback(script);document.head.append(script);}loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js",(script) => {alert(`the script ${script.src} is loaded`);});
如果要依次加载多个脚本,就在回调中回调
“Error 优先回调(error-first callback)”风格
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 ${script.src}`));document.head.append(script);}loadScript("/my/script.js", function (error, script) {if (error) {// 处理error} else {// 脚本加载成功}});
回调地狱
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 对象构造器语法:
let promise = new Promise(function (resolve, reject) {// executor});
传递给 new Promise 的函数被称为 executor。当 new Promise 被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。
它的参数 resolve 和 reject 是由 JavaScript 自身提供的回调。我们的代码仅在 executor 的内部。
executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve,如果出现 error 则调用 reject。
由 new Promise 构造器返回的 promise 对象具有以下内部属性:
- state — 最初是 “pending”,然后在 resolve 被调用时变为 “fulfilled”,或者在 reject 被调用时变为 “rejected”。
- result — 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。
与最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。
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
最重要最基础的一个就是 .then
语法如下:
promise.then(function (result) {/* handle a successful result */},function (error) {/* handle an error */});
.then 的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
.then 的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
如果我们只对成功完成的情况感兴趣,那么我们可以只为 .then 提供一个函数参数
catch
如果我们只对 error 感兴趣,那么我们可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用 .catch(errorHandlingFunction),其实是一样的:
let promise = new Promise((resolve, reject) => {setTimeout(() => reject(new Error("Whoops!")), 1000);});// .catch(f) 与 promise.then(null, f) 一样promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
finally
就像常规 try {…} catch {…} 中的 finally 子句一样,promise 中也有 finally。
.finally(f) 调用与 .then(f, f) 类似,在某种意义上,f 总是在 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)
也就是说,finally(f) 其实并不是 then(f,f) 的别名。它们之间有一些细微的区别:
finally 处理程序(handler)没有参数。在 finally 中,我们不知道 promise 是否成功。没关系,因为我们的任务通常是执行“常规”的定稿程序(finalizing procedures)。
finally 处理程序将结果和 error 传递给下一个处理程序。
我们可以根据需要,在 promise 上多次调用 .then
promise 链
我们有一系列的异步任务要一个接一个地执行,我们可以将 result 通过 .then 处理程序(handler)链进行传递。
new Promise(function (resolve, reject) {setTimeout(() => resolve(1), 1000); // (*)}).then(function (result) {// (**)alert(result); // 1return result * 2;}).then(function (result) {// (***)alert(result); // 2return result * 2;}).then(function (result) {alert(result); // 4return result * 2;});
对 promise.then 的调用会返回了一个 promise,当处理程序(handler)返回一个值时,它将成为该 promise 的 result,所以将使用它调用下一个 .then。
返回 promise
.then(handler) 中所使用的处理程序(handler)可以创建并返回一个 promise。
在这种情况下,其他的处理程序(handler)将等待它 settled 后再获得其结果(result)。
new Promise(function (resolve, reject) {setTimeout(() => resolve(1), 1000);}).then(function (result) {alert(result); // 1return new Promise((resolve, reject) => {// (*)setTimeout(() => resolve(result * 2), 1000);});}).then(function (result) {// (**)alert(result); // 2return new Promise((resolve, reject) => {setTimeout(() => resolve(result * 2), 1000);});}).then(function (result) {alert(result); // 4});
返回 promise 使我们能够构建异步行为链。
作为一个好的做法,异步行为应该始终返回一个 promise。这样就可以使得之后我们计划后续的行为成为可能。即使我们现在不打算对链进行扩展,但我们之后可能会需要。
