JS 异步编程

123 阅读6分钟

JS 采用单线程的原因

JS 最早是运行在浏览器端的脚本语言,它的目的是为了实现页面上的动态交互,而实现页面交互的核心是 dom 操作,这也决定了它必须使用单线程模型,否则会出现复杂的线程同步问题。假定 JS 中有多个线程同步工作,其中一个线程修改了某个 dom 元素,而另一个线程同时又删除了该元素,此时浏览器无法明确该以哪个线程工作的结果为准。

任务分类

  • 同步模式:任务依次执行,后面的任务必须等待前面的任务执行完毕,才能继续执行
  • 异步模式:不会等待当前耗时任务结束再去执行下个任务

JS 异步编程的解决方案

回调函数

由调用者定义,交给执行者执行的函数

  • 同步回调:立即执行,完全执行完成后才结束,不会放入到任务队列中,比如数组遍历相关的回调
  • 异步回调:不会立即执行,会放入到任务队列中,等将来再执行,比如定时器回调,ajax 回调,promise 回调

Promise

优势

纯回调函数在执行异步操作之前就已经指定好了异步回调的函数,而 promise 指定回调函数的方式更加灵活,可以在异步任务后再指定

链式调用

借助 promise.then() 链式调用的特点,保证异步任务的扁平化。promise.then() 返回的新的 promise 的结果状态由 then 指定的回调函数执行的结果决定

  • 如果抛出异常,新的 promise 变为 rejected,reason 为抛出的异常
  • 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值
  • 如果返回的是另一个新的 promise,此 promise 就会成为新的 promise 的结果

promise 串连多个操作任务

new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("执行任务1(异步)");
    resolve(1);
  }, 1000);
})
  .then((value) => {
    console.log("任务1的执行结果:", value);
    console.log("执行任务2(同步)");
    return 2; // 同步任务直接返回
  })
  .then((value) => {
    console.log("任务2的结果:", value);
    // 异步任务通过 promise 进行包裹
    return new Promise((resolve, reject) => {
      // 启动任务3(异步)
      setTimeout(() => {
        console.log("执行任务3(异步)");
        resolve(3);
      }, 1000);
    });
  })
  .then((value) => {
    console.log("任务3的结果", value);
  });

异常处理

2种异常处理的方式

ajax("/api/users.json").then(
  function onFulfilled(value) {
    console.log("onFulfilled", value);
    return ajax("/error-url");
  },
  function onRejected(error) {
    console.log("onRejected", error);
  }
);
ajax("/api/users.json")
  .then(function onFulfilled(value) {
    console.log("onFulfilled", value);
    return ajax("/error-url");
  })
  .catch(function onRejected(error) {
    console.log("onRejected", error);
  });

差异:每个 then 方法都是返回的一个全新的 promise 对象,catch 方法其实是给前面 then 方法返回的 promise 对象指定失败的回调,并不是给第一个 ajax 返回的 promise 对象指定的,只不过这是同一个 promise 链条,前面的 promise 异常一直会被往后传递。而通过 then 方法第二个参数指定的失败回调只是给第一个 promise 对象指定的。如果在 then 方法中返回的第二个 promise,并且在执行过程中也出现了异常,通过 then 第二个参数注册的失败回调是捕获不到的

除此之外,也可以在全局对象中注册 unhandledrejection 事件去处理没有手动被捕获的异常

// 错误可以一层层向外抛出,直到被最外层的catch捕获
new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1);
  }, 1000);
})
  .then((value) => {
    return 2;
  })
  .then((value) => {
    return 3;
  })
  .catch((value) => {
    console.log("错误", value);
  });

then 中没有写第二个错误回调,实则会隐式添加 reason=>{throw reason}

new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1);
  }, 1000);
})
  .then(
    (value) => {
      return 2;
    },
    (reason) => {
      throw reason; // reject(1) 触发第二个回调,向上抛出
    }
  )
  .then(
    (value) => {
      return 3;
    },
    (reason) => {
      throw reason; // 继续抛出
    }
  )
  .catch((value) => {
    console.log("错误", value);
  });

promise 异常穿透

当使用 promise 的 then 链式调用的时候,可以在最后指定失败的回调,前面任何操作出现了异常,都会传到最后失败的回调中进行处理

中断 promise 链 当使用 promise 的 then 链式调用时,想在中间中断,不再调用后面的回调函数,可以在回调函数中返回一个 pendding 状态的 promise 的对象

实现 Promise

  1. then 方法是可以被链式调用的, 后面 then 方法的回调函数拿到值的是上一个 then 方法的回调函数的返回值
  2. 一个 promise 对象可以指定多个成功/失败的回调函数,当 promise 对象改变为对应状态时都会调用,所以需要用数组存储成功/失败的回调函数
  3. then 方法的回调函数中不能返回自身调用的 promise 对象,会发生循环调用
  4. then 方法的回调函数是可选的,如果不传,相当于默认是 value => value,这样 value 值就可以一层层向后传递,直到传到最终有回调函数的地方
const PENDING = "pending"; // 等待
const FULFILLED = "fulfilled"; // 成功
const REJECTED = "rejected"; // 失败

class MyPromise {
  // new Promise 中的执行器
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }
  status = PENDING; // promise 状态
  value = undefined; // 成功之后的值
  reason = undefined; // 失败后的原因
  successCallback = []; // 成功回调
  failCallback = []; // 失败回调

  // resolve 箭头函数确保 this 指向 promise 对象
  resolve = (value) => {
    // 如果状态不是等待 阻止程序向下执行 因为 promise 状态一旦改变就不会再变化了
    if (this.status !== PENDING) return;
    this.status = FULFILLED; 
    this.value = value; // 保存成功之后的值,方便在then方法中使用
    while (this.successCallback.length) this.successCallback.shift()();
  };
  reject = (reason) => {
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    this.reason = reason; // 保存失败后的原因,方便在then方法中使用
    while (this.failCallback.length) this.failCallback.shift()();
  };
  then(successCallback, failCallback) {
    // 参数可选,不存在补充一个函数
    successCallback = successCallback ? successCallback : (value) => value;
    failCallback = failCallback
      ? failCallback
      : (reason) => {
          throw reason;
        };
    let promise2 = new MyPromise((resolve, reject) => {
      // 判断状态
      if (this.status === FULFILLED) {
        // 用 setTimeout 进行包装,变成异步代码,从而可以获取到 promise2
        setTimeout(() => {
          try {
            let x = successCallback(this.value);
            // 判断 x 的值是普通值还是promise对象
            // 如果是普通值 直接调用resolve
            // 如果是 promise 对象 查看 promise 对象返回的结果
            // 再根据 promise 对象返回的结果 决定调用 resolve 还是调用 reject
            // setTimeout 变成异步,确保拿到 promise2
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failCallback(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else {
        // pending 状态需要将成功回调和失败回调存储起来
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failCallback(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }
  finally(callback) {
    // finally 方法无论成功还是失败,都会执行
    return this.then(
      (value) => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      (reason) => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  }
  catch(failCallback) {
    return this.then(undefined, failCallback);
  }
  static all(array) {
    let result = [];
    let index = 0;
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value;
        index++;
        if (index === array.length) {
          resolve(result);
        }
      }
      for (let i = 0; i < array.length; i++) {
        let current = array[i];
        if (current instanceof MyPromise) {
          // promise 对象
          current.then(
            (value) => addData(i, value),
            (reason) => reject(reason)
          );
        } else {
          // 普通值
          addData(i, array[i]);
        }
      }
    });
  }
  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve) => resolve(value));
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 避免 promise 循环调用
  if (promise2 === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }
}

module.exports = MyPromise;