JavaScript 异步编程

0x00 概述

  • 同步(Synchronous)指任务顺序执行,上一个任务未完成前不会执行下一个任务

    console.log(1);
    console.log(2);
    console.log(3);
    
    // 1 2 3
    
  • 异步(Asynchronous)指任务无序并同时执行

    setTimeout(() => console.log(1), 300);
    setTimeout(() => console.log(2), 100);
    setTimeout(() => console.log(3), 200);
    
    // 2 3 1
    

0x01 回调函数

  • 回调(Callback)是异步编程的重要部分

  • 通过回调可以实现将异步的操作转换为同步

    function task1(callback) {
      setTimeout(() => {
        console.log(1);
        callback();
      }, 300);
    }
    function task2(callback) {
      setTimeout(() => {
        console.log(2);
        callback();
      }, 100);
    }
    function task3(callback) {
      setTimeout(() => {
        console.log(3);
        callback();
      }, 200);
    }
    
    task1(() => {
      task2(() => {
        task3(() => {
          console.log("end");
        });
      });
    });
    
  • 当上述案例中的异步任务越来越多,回调函数也就越来越长,形成回调地狱

    • 引入 Promise 解决

0x02 Promise

(1)概念与使用

  • Promise 是在 ES6 中引入的类,用于更好地编写复杂的异步任务

    • 在 ES6 之前的异步请求的处理方案中,通常通过传递回调函数的方式处理请求结果,由于各个请求对回调函数不统一,每次请求时都需要查看相应的源码,造成效率低下,因此需要约定共同的标准,即 Promise 类
  • 在通过 new 创建 Promise 对象时,需要传入一个回调函数(称为 executor)

    const newPromise = new Promise(
      (resolve, reject) => {}
    );
    

    这个回调函数会立即执行,并传入另外两个回调函数:

    1. resolve:执行 Promise 对象的 then 方法传入的回调函数
    2. reject:执行 Promise 对象的 catch 方法传入的回调函数

    举例:异步请求处理(使用计时器模拟网络请求)

    function requestData(url) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (url === "SRIGT") {
            resolve({
              data: ["SRIGT", "https://www.cnblogs.com/SRIGT"],
            });
          } else {
            reject({
              msg: "Invalid URL " + url,
            });
          }
        }, 1000);
      });
    }
    
    requestData("SR1GT").then(
      (res) => {
        console.log(res.data);
      },
      (err) => {
        console.log(err.msg);
      }
    );
    
  • Promise 在使用时,可被划分为三种状态:

    1. pending:待定中,即执行 executor()
    2. fulfilled / resolved:已决议,即执行 resolve()
    3. rejected:已拒绝,即执行 reject()
    new Promise((resolve, reject) => {
      // pending
    }).then(
      (res) => {
        // fulfilled  / resolved
      },
      (err) => {
        // rejected
      }
    );
    

    Promise 的状态一经确定则无法更改

  • resolve 方法参数包括:

    • 普通参数,如数字、字符串、数组、一般对象等

    • Promise 对象,则当前 Promise 对象的状态由传入的 Promise 对象决定

      new Promise((resolve, reject) => {
        resolve(
          new Promise((resolve, reject) => {
            reject("Error Message");
          })
        );
      }).then(
        (res) => {
          console.log("res:", res);
        },
        (err) => {
          console.log("err:", err);		// err: Error Message
        }
      );
      
    • 具有 then 方法的对象,则当前 Promise 对象的状态由传入的对象的 then 方法决定

      new Promise((resolve, reject) => {
        resolve({
          then: (resolve, reject) => {
            reject("Error Message");
          },
        });
      }).then(
        (res) => {
          console.log("res:", res);
        },
        (err) => {
          console.log("err:", err);		// err: Error Message
        }
      );
      

(2)对象方法

  • 查看 Promise 的所有对象方法

    console.log(Object.getOwnPropertyDescriptors(Promise.prototype));
    
    方法 可写性 可枚举性 可配置性
    constructor [Function: Promise] true false true
    then [Function: then] true false true
    catch [Function: catch] true false true
    finally [Function: finally] true false true
    [Symbol(Symbol.toStringTag)] 'Promise' false false true

a. then 方法

  • then 方法是 Promise 对上的方法,其在 Promise 的原型为 Promise.prototype.then

    • 接受两个参数,分别用于到达 fulfilled 或 rejected 状态时使用的回调函数
  • 同一个 Promise 对象可以被多次调用 then 方法

    • 当 Promise 中的 resolve 方法被调用时,所有 then 方法传入的回调函数都会执行
    const promise = new Promise((resolve, reject) => {
      resolve("Resolved");
    });
    
    promise.then((res) => console.log("res1:", res));
    promise.then((res) => console.log("res2:", res));
    promise.then((res) => console.log("res3:", res));
    
  • then 方法可以有返回值,是一个 Promise 对象,从而实现 Promise 的链式调用

    const promise = new Promise((resolve, reject) => {
      resolve("SRIGT");
    });
    
    promise
      .then((res) => {
        console.log(res);		// SRIGT
        return "srigt";
      })
      .then((res) => {
        console.log(res);		// srigt
      });
    

    此时,then 方法的 return 实际上遵循了 0x01 章关于 resolve 参数说明的内容

b. catch 方法

  • executor 抛出异常时,会调用错误处理的回调函数,此时,catch 方法用于传入处理错误的回调函数

    const promise = new Promise((resolve, reject) => {
      reject("Error Message");
    });
    
    promise.catch((err) => console.log(err));
    
  • catch 方法支持链式调用

    • 从外向内捕获错误并执行

      const promise = new Promise((resolve, reject) => {
        resolve("Success");
      });
      
      promise
        .then((res) => {
          console.log(res);							// Success
          return new Promise((resolve, reject) => {
            reject("Error 2");
          });
        })
        .catch((err) => console.log("err1:", err))	// Error 2
        .catch((err) => console.log("err2:", err));	// [此语句未执行]
      
    • then 方法在 catch 方法后也可以执行

      const promise = new Promise((resolve, reject) => {
        reject("Error");
      });
      
      promise
        .catch((err) => console.log("err1:", err))	// err1: Error
        .catch((err) => console.log("err2:", err))
        .then((res) => console.log("res:", res));		// res: undefined
      
  • catch 方法支持多次调用

  • catch 方法可以 return,返回的值是一个 Promise 对象

    const promise = new Promise((resolve, reject) => {
      reject("Error");
    });
    
    promise
      .then((res) => console.log("res1", res))
      .catch((err) => {
        console.log("err", err);					// err: Error
        return "Success";
      })
      .then((res) => console.log("res2", res));		// res2: Success
    

c. finally 方法

  • finally 方法是 ES9 新加入的特性,无论 fulfilled 还是 rejected 状态都会执行的方法

    const promise1 = new Promise((resolve, reject) => {
      resolve("Success");
    });
    const promise2 = new Promise((resolve, reject) => {
      reject("Error");
    });
    
    promise1.finally(() => console.log("Finally1"));
    promise2.finally(() => console.log("Finally2"));
    

Promises/A+ 规范:https://promisesaplus.com/

(3)Promise 类方法

a. resolve 与 reject

  • resolve

    const promise = Promise.resolve("Success");
    // 相当于
    const promise = new Promise((resolve, reject) => {
      resolve("Success");
    })
    
  • reject 使用与 resolve 类似

b. all 与 allSettled

  • all 方法将多个 Promise 包裹为一个新的 Promise

    • 等待队列(数组参数)中所有 Promise 均处于 fulfilled 状态时执行 then 方法

      const promise1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("Success1");
        }, 1000);
      });
      const promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("Success2");
        }, 2000);
      });
      const promise3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("Success3");
        }, 3000);
      });
      
      Promise.all([promise1, promise2, promise3, "All Over"]).then((res) =>
        console.log(res)		// [等待 3 秒后输出] [ 'Success1', 'Success2', 'Success3', 'All Over' ]
      );
      
    • 当队列中某个 Promise 处于 rejected 状态时,会中断执行并直接进入 catch 方法

      const promise1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("Success1");
        }, 1000);
      });
      const promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject("Error");
        }, 2000);
      });
      const promise3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("Success3");
        }, 3000);
      });
      
      Promise.all([promise1, promise2, promise3, "All Over"])
        .then((res) => console.log(res))
        .catch((err) => console.log(err));		// [等待 2 秒后输出] Error
      
  • allSettled 方法在 ES11 中加入,不会因为某个 Promise 为 rejected 状态而中断,且最终状态一定为 fulfilled

    const promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("Success1");
      }, 1000);
    });
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("Error");
      }, 2000);
    });
    const promise3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("Success3");
      }, 3000);
    });
    
    Promise.allSettled([promise1, promise2, promise3, "All Over"])
      .then((res) => console.log(res));
    
    /*
     * [
     *   { status: 'fulfilled', value: 'Success1' },
     *   { status: 'rejected', reason: 'Error' },
     *   { status: 'fulfilled', value: 'Success3' },
     *   { status: 'fulfilled', value: 'All Over' }
     * ]
     */
    

c. race 与 any

  • race 方法特点是:只要有一个 Promise 不是 pending 状态,则中断执行

    const promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("Error1");
      }, 1000);
    });
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("Error2");
      }, 2000);
    });
    
    Promise.race([promise1, promise2])
      .then((res) => console.log(res))
      .catch((err) => console.log(err));	// [等待 1 秒后执行] Error
    
  • any 方法在 ES12 中加入,只要有一个 Promise 是 fulfilled 状态,则中断执行

    const promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("Error1");
      }, 1000);
    });
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("Error2");
      }, 2000);
    });
    const promise3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("Success");
      }, 3000);
    });
    
    Promise.any([promise1, promise2, promise3])
      .then((res) => console.log(res))		// [等待 3 秒后执行] Success
      .catch((err) => console.log(err));
    
    • 如果所有 Promise 都是 rejected 状态,则等到所有 Promise 执行结束

      const promise1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject("Error1");
        }, 1000);
      });
      const promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject("Error2");
        }, 2000);
      });
      const promise3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject("Error3");
        }, 3000);
      });
      
      Promise.any([promise1, promise2, promise3])
        .then((res) => console.log(res))
        .catch((err) => console.log(err.errors));		// [等待 3 秒后执行] [ 'Error1', 'Error2', 'Error3' ]
      

(4)MyPromise

  1. 初步构建 MyPromise,通过 status 保存状态、result 保存结果、_this 保存 MyPromise,配置原型方法 then 并测试

    function MyPromise(executor) {
      this.status = "pending";
      this.result = undefined;
      const _this = this;
      function resolve(res) {
        if (_this.status !== "pending") return;
        _this.status = "fulfilled";
        _this.result = res;
      }
      function reject(res) {
        if (_this.status !== "pending") return;
        _this.status = "rejected";
        _this.result = res;
      }
      executor(resolve, reject);
    }
    
    MyPromise.prototype.then = function (successCallback, failureCallback) {   
      if (!successCallback) successCallback = value => value;
      if (!failureCallback) failureCallback = error => error;
    
      if (this.status === "fulfilled") {
        successCallback(this.result);
      } else if (this.status === "rejected") {
        failureCallback(this.result);
      }
    };
    

    测试

    const promise = new MyPromise((resolve, reject) => {
      resolve("Success");
      reject("Error");
    }).then(
      (res) => console.log(res),
      (err) => console.log(err)
    );
    
  2. 当在 pending 状态下执行时间较长,则需要收集回调,等待完成后再执行回调;为防止多次调用 Promise 的 then 方法造成后者覆盖前者,采用数组保存回调的收集

    function MyPromise(executor) {
      this.status = "pending";
      this.result = undefined;
      this.stack = [];
      const _this = this;
      function resolve(res) {
        if (_this.status !== "pending") return;
        _this.status = "fulfilled";
        _this.result = res;
        _this.stack.forEach((item) => item.successCallback(res));
      }
      function reject(res) {
        if (_this.status !== "pending") return;
        _this.status = "rejected";
        _this.result = res;
        _this.stack.forEach((item) => item.failureCallback(res));
      }
      executor(resolve, reject);
    }
    
    MyPromise.prototype.then = function (successCallback, failureCallback) {
      if (!successCallback) successCallback = value => value;
      if (!failureCallback) failureCallback = error => error;
    
      if (this.status === "fulfilled") {
        successCallback(this.result);
      } else if (this.status === "rejected") {
        failureCallback(this.result);
      } else {
        this.stack.push({
          successCallback,
          failureCallback,
        });
      }
    };
    

    测试

    const promise1 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve("Success2");
      }, 1000);
    }).then(
      (res) => console.log(res),
      (err) => console.log(err)
    );
    const promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve("Success2");
      }, 1000);
    }).then(
      (res) => console.log(res),
      (err) => console.log(err)
    );
    
  3. 实现链式调用

    function MyPromise(executor) {
      this.status = "pending";
      this.result = undefined;
      this.stack = [];
      const _this = this;
      function resolve(res) {
        if (_this.status !== "pending") return;
        _this.status = "fulfilled";
        _this.result = res;
        _this.stack.forEach((item) => item.successCallback(res));
      }
      function reject(res) {
        if (_this.status !== "pending") return;
        _this.status = "rejected";
        _this.result = res;
        _this.stack.forEach((item) => item.failureCallback(res));
      }
      executor(resolve, reject);
    }
    
    MyPromise.prototype.then = function (successCallback, failureCallback) {
      if (!successCallback) successCallback = value => value;
      if (!failureCallback) failureCallback = error => error;
    
      return new MyPromise((resolve, reject) => {
        if (this.status === "fulfilled") {
          const result = successCallback(this.result);
          if (result instanceof MyPromise) {
            result.then(
              (res) => resolve(res),
              (err) => reject(err)
            );
          } else {
            resolve(result);
          }
        } else if (this.status === "rejected") {
          const result = failureCallback(this.result);
          if (result instanceof MyPromise) {
            result.then(
              (res) => resolve(res),
              (err) => reject(err)
            );
          } else {
            reject(result);
          }
        } else {
          this.stack.push({
            successCallback: () => {
              const result = successCallback(this.result);
              if (result instanceof MyPromise) {
                result.then(
                  (res) => resolve(res),
                  (err) => reject(err)
                );
              } else {
                resolve(result);
              }
            },
            failureCallback: () => {
              const result = failureCallback(this.result);
              if (result instanceof MyPromise) {
                result.then(
                  (res) => resolve(res),
                  (err) => reject(err)
                );
              } else {
                reject(result);
              }
            },
          });
        }
      });
    };
    

    测试

    const promise = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve("Success1");
      }, 1000);
    })
      .then(
        (res) => {
          console.log("res1:", res);
          return new MyPromise((resolve, reject) => {
            setTimeout(() => {
              reject("Error2");
            }, 1000);
          });
        },
        (err) => console.log("err1:", err)
      )
      .then(
        (res) => console.log("res2:", res),
        (err) => console.log("err2:", err)
      );
    
  4. 实现 catch 方法

    MyPromise.prototype.catch = function (failureCallback) {
      this.then(undefined, failureCallback);
    };
    
  5. 完整自制的 MyPromise

    function MyPromise(executor) {
      this.status = "pending";
      this.result = undefined;
      this.stack = [];
      const _this = this;
      function resolve(res) {
        if (_this.status !== "pending") return;
        _this.status = "fulfilled";
        _this.result = res;
        _this.stack.forEach((item) => item.successCallback(res));
      }
      function reject(res) {
        if (_this.status !== "pending") return;
        _this.status = "rejected";
        _this.result = res;
        _this.stack.forEach((item) => item.failureCallback(res));
      }
      executor(resolve, reject);
    }
    
    MyPromise.prototype.then = function (successCallback, failureCallback) {
      if (!successCallback) successCallback = () => {};
      if (!failureCallback) failureCallback = () => {};
    
      return new MyPromise((resolve, reject) => {
        if (this.status === "fulfilled") {
          const result = successCallback(this.result);
          if (result instanceof MyPromise) {
            result.then(
              (res) => resolve(res),
              (err) => reject(err)
            );
          } else {
            resolve(result);
          }
        } else if (this.status === "rejected") {
          const result = failureCallback(this.result);
          if (result instanceof MyPromise) {
            result.then(
              (res) => resolve(res),
              (err) => reject(err)
            );
          } else {
            reject(result);
          }
        } else {
          this.stack.push({
            successCallback: () => {
              const result = successCallback(this.result);
              if (result instanceof MyPromise) {
                result.then(
                  (res) => resolve(res),
                  (err) => reject(err)
                );
              } else {
                resolve(result);
              }
            },
            failureCallback: () => {
              const result = failureCallback(this.result);
              if (result instanceof MyPromise) {
                result.then(
                  (res) => resolve(res),
                  (err) => reject(err)
                );
              } else {
                reject(result);
              }
            },
          });
        }
      });
    };
    
    MyPromise.prototype.catch = function (failureCallback) {
      this.then(undefined, failureCallback);
    };
    

0x03 async 与 await

  • asyncawait 在 ES7 中加入,使异步代码看起来和写起来更像是同步代码

  • async 返回 Promise 对象,而 await 接收 Promise 对象并返回该对象的结果(如果不是 Promise 对象则返回对应的值)

    async function requestData() {
      const result = await new Promise((resolve, reject) => {
        resolve("Success");
      });
      return result;
    }
    const result = requestData();
    console.log(result);
    result.then(
      (res) => console.log(res),
      (err) => console.log(err)
    );
    

-End-

posted @ 2024-07-19 11:17  SRIGT  阅读(10)  评论(0编辑  收藏  举报