likes
comments
collection
share

异步的发展(下)

作者站长头像
站长
· 阅读数 25

异步(下)

前言

在现代 JavaScript 编程中,异步编程是必不可少的部分。上篇文章中我们已经讨论了回调和 Promise。在这篇文章中,我们将探讨 Generator 函数和 async/await,并介绍它们如何在异步编程中发挥作用。

Generator函数

Generator 函数是 ES6 引入的一种新的函数形式,它允许函数在执行过程中暂停,并在之后恢复执行。与普通函数不同,Generator 函数会返回一个迭代器对象,通过这个迭代器对象的 next() 方法,可以逐步执行函数的代码。

Generator 函数使用 function* 关键字定义,函数内部使用 yield 关键字来暂停执行。

下面看一个简单的示例:

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: undefined, done: true }

调用 Generator 函数返回的迭代器对象的 next() 方法时,会返回一个对象,该对象包含两个属性:

  • value:当前 yield 表达式的值。
  • done:一个布尔值,表示 Generator 函数是否已执行完毕。

值得注意的是value是指当前 yield 表达式的值,这是什么意思呢,是执行yield返回值的结果嘛?

我们来看下面这个代码,我们用变量a,b承接yield的返回值并打印

function* gen() {
  let a = yield 1;
  console.log(a);
  let b = yield 2;
  console.log(b);
}

const g = gen();
console.log(g.next()); 
console.log(g.next("这是打印a的结果"));
console.log(g.next("这是打印b的结果"));

结果:

{ value: 1, done: false }
这是打印a的结果
{ value: 2, done: false }
这是打印b的结果
{ value: undefined, done: true }

从这个代码中,我们可以知道

  • yield 表达式的值:当 Generator 函数执行到 yield 表达式时,它会暂停执行,并将 yield 表达式后面的值作为当前的 value 返回。直到下一次调用 next() 方法时,Generator 函数才会继续执行。

  • yield 语句的返回值:在 yield 表达式的返回值是调用 next() 方法时传入的参数。

那么通过这种语法特点是如何实现异步的

要知道Generator 函数本身是同步的,但它们可以与 yield 关键字和一些控制逻辑结合,来实现异步操作的顺序执行。通过结合 yieldPromise,可以在 Generator 函数中处理异步操作。

下面是一个示例:

function A() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("异步a");
      resolve();
    }, 1000);
  });
}

function B() {
  console.log("同步b");
}

function* gen() {
  yield A();
  yield B();
}

function runGenerator(gen) {
  const iterator = gen();

  function handleResult(result) {
    if (result.done) return;

    const value = result.value;
    if (value instanceof Promise) {
      value.then(res => handleResult(iterator.next(res)))
           .catch(err => iterator.throw(err));
    } else {
      handleResult(iterator.next(value));
    }
  }

  handleResult(iterator.next());
}

runGenerator(gen);

// 结果:
// 异步a
// 同步b

在这个示例里:

  1. 定义一个辅助函数 runGenerator,它接收一个生成器函数 gen 作为参数。

  2. 创建生成器对象 iterator 并启动生成器函数。

  3. 定义一个内部函数 handleResult 来处理生成器的执行结果。

  4. 检查生成器返回的结果是否完成 (result.done)。

  5. 如果生成器返回一个 Promise,等待其解决并递归调用 handleResult 继续执行生成器。

  6. 如果生成器返回一个非 Promise 值,直接递归调用 handleResult 继续执行生成器。

通过这种方式,你可以让生成器函数与异步操作一起工作,实现异步控制流。

但是使用生成器函数和辅助函数来处理异步操作的缺点:

  1. 复杂性高:这种方法需要额外的辅助函数来处理生成器的执行结果,使得代码的复杂性增加。

  2. 错误处理麻烦:虽然可以在辅助函数中捕获错误并抛出,但处理起来并不直观,特别是在复杂的异步流程中。

基于上述暂停函数执行的想法,就有了async/await这种方式,对于它的内部实现逻辑就是与Generator 函数有关,有兴趣的小伙伴可以阅读源码进行复刻。

async/await

async/await 是 ES2017 引入的语法,用于简化基于 Promise 的异步编程。async 关键字用于定义一个异步函数,该函数会隐式地返回一个 Promise。await 关键字用于暂停异步函数的执行,等待一个 Promise 解析,并返回解析值。

function fetchData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Data from ${url}`);
    }, 1000);
  });
}

async function getData() {
  try {
    const data1 = await fetchData('url1');
    console.log(data1);
    const data2 = await fetchData('url2');
    console.log(data2);
  } catch (error) {
    console.error('Error:', error);
  }
}

getData();

// Data from url1
// Data from url2

fetchData 函数返回一个 Promise,模拟异步数据获取。getData 是一个异步函数,使用 await 暂停执行,等待 fetchData 的 Promise 解析,并打印获取的数据。

最后

异步发展至今,async/await已经是非常优雅的方式了,在实际编程实践中,我们肯定也是直接用async/await来实现异步操作啦。

转载自:https://juejin.cn/post/7397028381960847411
评论
请登录