异步编程

JS 引擎使用单线程执行代码,某一时刻只能执行一段代码,不能并行执行代码。执行代码时,JS 引擎会把将要执行的代码段放入作业队列中,当前代码执行完毕后,会从队列中取出代码段并执行,循环上述操作,直到执行完所有需执行的代码。

每个事件与一个处理该事件的代码段关联,一般情况下这个代码段组织成函数。当事件发生时,该代码段被添加到作业队列中,作业队列中前面的作业都执行完毕后,执行该代码段从而处理关联的事件。事件与处理程序必须在该事件发生之前绑定,不然事件发生时该程序不会执行。

回调函数是一个代码段,作为参数传入一个函数中,该函数在执行过程中因为某种条件未满足需要等待时,js 引擎不会等待该函数,而是会继续执行该函数下面的语句。当条件满足后,该函数恢复执行。一般情况下条件满足时执行回调函数。例如,一个读取磁盘文件的函数执行时,需要等待读取完成(不论成功还是失败)才能继续执行。当调用其他程序读取磁盘时,该函数需要等待,直到返回读取结果。结果返回后,该函数才能接着执行。在其他程序读取磁盘过程中,js 引擎会执行该函数下面的语句。读取磁盘的结果返回时,回调函数及作为其参数的返回结果将被加入到作业队列中,后续由 js 引擎执行完队列中前面的所有作业后将其从队列中取出执行。

Promise 基础

Promise 表示一个异步操作。异步操作没有完成时,Promise 的状态为挂起态 (pending state),称为 unsettled;异步操作完成时,称为 settled,此时 Promise 有两种可能的状态:

  • 已完成:异步操作成功结束。
  • 已拒绝:因某种原因,异步操作未完成结束。

Promise 的完整生命周期为 unsettled 到 settled,合计三种状态。允许执行的操作取决于 Promise 的当前状态。具体三种状态由 [[PromiseState]] 属性设置,该属性不可读写。

Promise 的 then() 方法接收两个可选的参数,这两个参数都是函数,当 Promise 的状态为已完成时,第一个参数执行,异步操作成功结束的结果作为一个参数传入该函数;当 Promise 的状态为已拒绝时,第二个参数执行,异步操作未成功结束的结果作为一个参数传入该函数。使用上述方式实现 then() 的任何对象都称为一个 thenable,Promise 是 thenable,反之不然。

let promise = asyFun(); // 执行一个有异步操作的函数,下面是三种处理情形。
promise.then((content) => {
  console.log(content);
}, err => {
  console.error(err.message);
});

promise.then(content => {});   // 传入前面的参数
promise.then(null, err => {}); // 传入后面的参数

Promise 的 catch() 在状态为已拒绝时执行,相当于 then() 仅传入后一个参数的情形。

Promise 允许在调用处理函数时,再次创建 Promise 的处理函数。这个时候,新的处理函数被添加到作业队列中,前面的作业处理完毕后,调用该处理函数。Promise 使用一个单独的作业队列,只添加 Promise 相关的处理函数。每次调用 then()catch() 时,会创建新的作业,Promise 为 settled 时,从队列中取出执行。

promise.then(content => {
  console.log(content);
  promise.then(content => {},
    err => {});
});

Promise 的构造器有一个参数,是一个称为执行器的函数,里面包含初始化 Promise 的代码。执行器有两个参数,名称分别为 resolve 和 reject。这两个参数也是函数,当执行器成功完成时,resolve() 被调用;未成功完成时,reject() 被调用。通常执行器里的代码是一个异步操作,异步操作的结果决定是 resolve() 还是 reject() 被调用。

在执行构造器时,执行器会立即执行。异步操作类似于 setTimeout(() => {}, 1000)

// 即使延时设为 0,setTimeout 内部的函数依然需要先加入队列再执行,输出结果为
// 2
// 1
setTimeout(() => console.log(1), 0);
console.log(2);

在执行器内部调用 resolve() 时,会触发异步操作,then()catch() 中的参数根据异步操作结果被调用。

// 输出为:
// promise
// out
// content
let promise = new Promise((resolve, reject) => {
  console.log("promise");
  resolve();
});

promise.then(() => {
  console.log("content");
});
console.log("out");

Promise.resolve() 方法接收一个参数,这个参数将作为已完成状态的 Promise 的结果返回,可以使用 then() 处理。Promise.reject() 方法接收一个参数,这个参数将作为已拒绝状态的 Promise 的结果返回,可以使用 catch() 处理。

将状态为挂起或已完成的 Promise 传入 Promise.resolve() 时返回原 Promise;将状态为已拒绝的 Promise 传入 Promise.reject() 时返回原 Promise;其他情况返回包装原 Promise 的新 Promise。

拥有以 resolve 和 reject 为参数的 then() 方法的对象是非 Promise 的 thenable。将非 Promise 的 thenable 传入 Promise.then()Promise.reject() 时,调用对象的 then() 中执行的方法确定返回哪种状态的 Promise。

let thenable = {
  then(resolve, reject) {
    resolve(3);
  }
};

let p1 = Promise.resolve(thenable);   // 状态为已完成的 Promise
p1.then(value => console.log(value)); // 3

let th2 = {
  then(resolve, reject) {
    reject(7);
  }
};
let p2 = Promise.resolve(th2);     // 状态为已拒绝的 Promise
p2.catch(err => console.log(err)); // 7

执行器内部抛出错误时,reject() 方法会被调用,捕获这个错误。

全局 Promise 拒绝处理

具有状态为已拒绝且没有得到处理的 Promise 需要有办法来识别。

Node.js 中 process 对象有两个相关的事件。

  • unhandledRejection:当状态为已拒绝的 Promise 在事件循环的一个轮次中没有被处理时,该事件会被触发。这个事件函数有两个参数:第一个为拒绝原因,第二个为已拒绝的 Promise。
  • rejectionHandled:当状态为已拒绝的 Promise 在事件循环的一个轮次之后被处理时,该事件会被触发。这个事件函数有一个参数,表示已拒绝的 Promise。

浏览器提供的事件触发条件与事件名称与 Node.js 一样。唯一不同的是浏览器提供的事件函数参数为一个对象,该对象有三个属性:事件函数对应的事件类型(type)、已拒绝的 Promise (promise)、拒绝原因(reason)。

串联 Promise

Promise 的 then()catch() 方法被调用完成后会创建并返回一个新的 Promise,根据这个特点可以连续使用 Promise 形成链。

在 Promise 链中,前一个 Promise 在执行器、resolve()reject() 中抛出(发生)的错误,可以由紧接着的 Promise 捕获在 reject() 中处理。

在 Promise 链中,前一个 Promise 在 resolve()reject() 中返回的值会传入到紧接着的 Promise 的 resolve() 方法中。

在 Promise 链中,前一个 Promise 在 resolve()reject() 中返回的 Promise 对象充当了紧接着的 Promise 对象。

响应多个 Promise

Promise.all() 方法接收一个可迭代对象,这个对象的每个元素都是一个 Promise。当每个元素的状态都为已完成时,该方法返回一个 Promise,这个 Promise 的 resolve() 方法参数是一个数组,这个数组的元素为传入的每个 Promise 的 resolve() 参数值,元素顺序与传入 Promise.all() 时的 Promise 顺序一致。或者当传入的 Promise 中如果有一个 Promise 的状态为已拒绝,则该方法返回的 Promise 状态为已拒绝,reject() 方法的参数为状态是已拒绝的 Promise 的 reject() 的参数值。

Promise.race() 方法接收的参数类型与 Promise.all() 一样。区别在于,接收的所有 Promise 中,如果有一个 Promise 的状态变为已完成或已拒绝,则该方法会返回一个新的 Promise,这个 Promise 的状态取决于状态变更到哪种状态,对应状态的方法参数与变更的 Promise 的方法参数值一致。

继承 Promise

将 Promise 作为基类通过继承定义自定义 Promise 可以扩展 Promise 的功能。自定义 Promise 继承了静态方法 resolve()reject()race()all()。后两个方法行为与 Promise 一致;前两个方法返回自定义 Promise 对象,由 [[Symbol.species]] 属性决定返回类型。

异步任务运行

function run(generator) {
  let iterator = generator();
  let result = iterator.next();
  (function step(){
    if (!result.done) {
      let p = Promise.resolve(result.value);
      p.then(value => {
        result = iterator.next(value);
        step();
      }).catch(error => {
        result = iterator.throw(error);
        step();
      });
    }
  }());
}

function asy(name) {
  return new Promise((resolve, reject) => {
    read(name, (result, err) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
}

参考

[1] Zakas, Understanding ECMAScript 6, 2017.

 posted on 2024-06-14 12:58  x-yun  阅读(4)  评论(0编辑  收藏  举报