js异步编程

JavaScript 是一种单线程语言,主要是防止 dom 操作的不确定性。但是在同步执行任务的过程容易受到一些耗时任务的阻塞,比如网络请求、定时器和事件监听等,这样会影响到整体页面的加载;因此需要引入异步编程的能力

回调函数

回调地狱:如果某个业务,依赖于上层业务的数据,上层业务又依赖于更上一层的数据,我们还采用回调的方式来处理异步的话,就会出现回调地狱。

function f2(func) {
  console.log("f2");
  setTimeout(function () {
    func();
  }, 500);
}
function func() {
  console.log("func");
}

promise

Promise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise 的 then() 方法,如果处理流程复杂的话,整段代码将充满 then

function demo() {
  new Promise(function (resolve, reject) {
    fetch(url)
      .then((data) => {
        resolve(data);
      })
      .catch();
  });
}
demo()
  .then((data) => {})
  .catch();

其实 promise 本质上使用了观察者模式,主要流程是 then 函数收集回调函数—>同步/异步触发 resolve 函数—>resolve 执行回调

先看下 promise 的几种基础特性:

  • promise 主要有三种状态,状态间的转移是不可逆的,只能有 pengding to resolved 或者 pending to rejected 两种变化
    • pending
    • resolved
    • rejected
  • then 函数接收两个参数,分别是成功回调和失败回调
  • then 会返回一个 promise,并且会返回上一次处理 resolve 的结果,即链式调用
  • 捕获错误

基于这三个特性,尝试设计一个 promise

实现状态和 then 函数

function MiniPromise(fn) {
  this.status = "pending"; // 状态
  this.data = null; // 成功信息
  this.err = null; // 错误信息
  this.onFulfillCallbacks = []; // 成功回调
  this.onRejectCallbacks = []; // 成功回调

  let self = this;

  function resolve(data) {
    // 确保状态单向转移
    if (self.status === "pending") {
      self.status = "resolved";
      self.data = data;
    }
    // 执行回调
    self.onFulfillCallbacks.forEach((onFulfillCallback) => {
      onFulfillCallback();
    });
  }
  function reject(err) {
    if (self.status === "pending") {
      self.status = "rejected";
      self.err = err;
    }
    self.onRejectCallbacks.forEach((onRejectCallback) => {
      onRejectCallback();
    });
  }

  try {
    fn(resolve, reject);
  } catch (e) {
    console.log(e);
  }
}

MiniPromise.prototype.then = function (onFulfill, onReject) {
  let self = this;
  if (self.status === "pending") {
    // 将执行回调存入回调数组
    self.onFulfillCallbacks.push(function () {
      onFulfill(self.data);
    });
    self.onRejectCallbacks.push(function () {
      onReject(self.err);
    });
  }
  if (self.status === "resolved") {
    onFulfill(self.data);
  }
  if (self.status === "rejected") {
    onReject(self.err);
  }
};

let miniPromise = new MiniPromise(function (resolve, reject) {
  setTimeout(() => {
    resolve("success");
  }, 1000);
});
miniPromise.then((data) => {
  console.log(data);
});
// 1s后输出'success'

实现链式调用

then 函数应该要先判断传入的参数,如果是值类型直接返回该值,如果是函数或者对象,则返回一个新的 promise。下面稍微改造下 then 函数

MiniPromise.prototype.then = function (onFulfill, onReject) {
  let self = this;
  let promise = new MiniPromise(function (resolve, reject) {
    if (self.status === "pending") {
      // 将执行回调存入回调数组
      self.onFulfillCallbacks.push(function () {
        try {
          let value = onFulfill(self.data);
          // 将value作为下一个then函数的参数
          resolve(value);
        } catch (err) {
          reject(err);
        }
      });
      self.onRejectCallbacks.push(function () {
        try {
          let err = onReject(self.err);
          reject(err);
        } catch (err) {
          reject(err);
        }
      });
    }
    if (self.status === "resolved") {
      try {
        let value = onFulfill(self.data);
        resolve(value);
      } catch (err) {
        reject(err);
      }
    }
    if (self.status === "rejected") {
      try {
        let err = onReject(self.err);
        reject(err);
      } catch (err) {
        reject(err);
      }
    }
  });
  return promise;
};
// 正常链式调用
miniPromise
  .then((data) => {
    console.log(data);
    return `1_data`;
  })
  .then((data) => {
    console.log(data);
  });
// 输出 success
// 输出 1_success

// 捕获第一次执行then的错误
miniPromise
  .then((data) => {
    console.log(data);
    a = b;
    return `1_${data}`;
  })
  .then(
    (data) => {
      console.log(data);
    },
    (err) => {
      console.log(err.message);
    }
  );

Generator

Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行

function* getNum() {
  yield 1;
  yield 2;
  return 3;
}
const gen = getNum();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

Generator 是如何实现的呢?比如上面这段代码,通过 babel 转移后的代码如下:

function getNum() {
  return regeneratorRuntime.wrap(function getNum$(_context) {
    while (1) {
      switch ((_context.prev = _context.next)) {
        case 0:
          _context.next = 2;
          return 1;

        case 2:
          _context.next = 4;
          return 2;

        case 4:
          return _context.abrupt("return", 3);

        case 5:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

var gen = getNum();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

其实 Generator 实现的核心就在于上下文的保存,每一次执行到 yield,其实都执行了一遍传入的生成器函数(getNum$),只是在这个过程中间用了一个 context 对象储存上下文,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行

async 和 await

  • 更加语义化
  • 内置执行器
  • 返回 promise
  • await 会等待这个 Promise (也可以是任意表达式)完成,并将其 resolve 的结果返回
async function getUser() {
  const user = await getUser();
  return user;
}
getUser().then((res) => console.log(res));

async/await 的实现原理就是 generator 自动执行器 + promise

// generator 自动执行器
function getNumPromise(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num);
    }, 1000);
  });
}

function* getNum() {
  const f1 = yield getNumPromise(1);
  const f2 = yield getNumPromise(f1 + 1);
  return yield getNumPromise(f2 + 1);
}

function run(gen) {
  // 生成一个迭代器
  const g = gen();

  function next(data) {
    let result = g.next(data);
    if (result.done) {
      return result.value;
    }
    result.value.then((data) => {
      console.log(data);
      next(data);
    });
  }

  next();
}

run(getNum);

可以通过 babel 查看转译 async 后的结果,其实跟上面内容差不多的