回调

“基于回调”的异步编程风格: 异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用

  1. function loadScript(src, callback) {
  2. let script = document.createElement("script");
  3. script.src = src;
  4. script.onload = () => callback(script);
  5. document.head.append(script);
  6. }
  7. loadScript(
  8. "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js",
  9. (script) => {
  10. alert(`the script ${script.src} is loaded`);
  11. }
  12. );

如果要依次加载多个脚本,就在回调中回调
“Error 优先回调(error-first callback)”风格

  1. function loadScript(src, callback) {
  2. let script = document.createElement("script");
  3. script.src = src;
  4. script.onload = callback(null, script);
  5. script.onerror = callback(new Error(`Script load error for ${script.src}`));
  6. document.head.append(script);
  7. }
  8. loadScript("/my/script.js", function (error, script) {
  9. if (error) {
  10. // 处理error
  11. } else {
  12. // 脚本加载成功
  13. }
  14. });

回调地狱

  1. loadScript("1.js", function (error, script) {
  2. if (error) {
  3. handleError(error);
  4. } else {
  5. // ...
  6. loadScript("2.js", function (error, script) {
  7. if (error) {
  8. handleError(error);
  9. } else {
  10. // ...
  11. loadScript("3.js", function (error, script) {
  12. if (error) {
  13. handleError(error);
  14. } else {
  15. // ...加载完所有脚本后继续 (*)
  16. }
  17. });
  18. }
  19. });
  20. }
  21. });

promise

Promise 对象构造器语法:

  1. let promise = new Promise(function (resolve, reject) {
  2. // executor
  3. });

传递给 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 的调用都会被忽略:

  1. let promise = new Promise(function (resolve, reject) {
  2. resolve("done");
  3. reject(new Error("…")); // 被忽略
  4. setTimeout(() => resolve("…")); // 被忽略
  5. });

Promise 对象的 state 和 result 属性都是内部的。我们无法直接访问它们。但我们可以对它们使用 .then/.catch/.finally 方法。

then

最重要最基础的一个就是 .then
语法如下:

  1. promise.then(
  2. function (result) {
  3. /* handle a successful result */
  4. },
  5. function (error) {
  6. /* handle an error */
  7. }
  8. );

.then 的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
.then 的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。

如果我们只对成功完成的情况感兴趣,那么我们可以只为 .then 提供一个函数参数

catch

如果我们只对 error 感兴趣,那么我们可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)。或者我们也可以使用 .catch(errorHandlingFunction),其实是一样的:

  1. let promise = new Promise((resolve, reject) => {
  2. setTimeout(() => reject(new Error("Whoops!")), 1000);
  3. });
  4. // .catch(f) 与 promise.then(null, f) 一样
  5. 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)。

  1. new Promise((resolve, reject) => {
  2. /* 做一些需要时间的事儿,然后调用 resolve/reject */
  3. })
  4. // 在 promise 为 settled 时运行,无论成功与否
  5. .finally(() => stop loading indicator)
  6. // 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止
  7. .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)链进行传递。

  1. new Promise(function (resolve, reject) {
  2. setTimeout(() => resolve(1), 1000); // (*)
  3. })
  4. .then(function (result) {
  5. // (**)
  6. alert(result); // 1
  7. return result * 2;
  8. })
  9. .then(function (result) {
  10. // (***)
  11. alert(result); // 2
  12. return result * 2;
  13. })
  14. .then(function (result) {
  15. alert(result); // 4
  16. return result * 2;
  17. });

对 promise.then 的调用会返回了一个 promise,当处理程序(handler)返回一个值时,它将成为该 promise 的 result,所以将使用它调用下一个 .then。

返回 promise

.then(handler) 中所使用的处理程序(handler)可以创建并返回一个 promise。
在这种情况下,其他的处理程序(handler)将等待它 settled 后再获得其结果(result)。

  1. new Promise(function (resolve, reject) {
  2. setTimeout(() => resolve(1), 1000);
  3. })
  4. .then(function (result) {
  5. alert(result); // 1
  6. return new Promise((resolve, reject) => {
  7. // (*)
  8. setTimeout(() => resolve(result * 2), 1000);
  9. });
  10. })
  11. .then(function (result) {
  12. // (**)
  13. alert(result); // 2
  14. return new Promise((resolve, reject) => {
  15. setTimeout(() => resolve(result * 2), 1000);
  16. });
  17. })
  18. .then(function (result) {
  19. alert(result); // 4
  20. });

返回 promise 使我们能够构建异步行为链。
作为一个好的做法,异步行为应该始终返回一个 promise。这样就可以使得之后我们计划后续的行为成为可能。即使我们现在不打算对链进行扩展,但我们之后可能会需要。