【JavaScript】这一次,彻底搞懂 JS 异步及其演进历程 ~(三)

简介: 【JavaScript】这一次,彻底搞懂 JS 异步及其演进历程 ~(三)

4、Generator / yield

JavaScript Generator 是一个相对较新的概念,它们在ES6(也被称为ES2015)中被引入。它是一种特殊类型的函数,它可以被暂停和恢复运行,并且可以通过 yield 语句向调用方返回数据。结合 Promise 使用,可以实现异步编程。

先来看一个简单的例子:

function* foo () {  
    var index = 0;
    while (index < 2) {
      yield index++;
    }
}
var bar =  foo();
console.log(bar.next());    // { value: 0, done: false }  
console.log(bar.next());    // { value: 1, done: false }  
console.log(bar.next());    // { value: undefined, done: true }  

Generator 函数执行时,会返回一个迭代器对象,该迭代器对象可以通过 next() 方法向下执行生成器函数。在 Generator 函数中,通过 yield 关键字可以将代码的执行权交回给调用者,并将 yield 后的表达式作为参数传递给调用者。这样就实现了协作式多任务处理。

可以看到,generator函数有一个最大的特点,可以在内部执行的过程中交出程序的控制权,yield相当于起到了一个暂停的作用;而当一定情况下,外部又将控制权再移交回来。

想象一下,我们用generator来封装代码,在异步任务处使用yield关键词,此时generator会将程序执行权交给其他代码,而在异步任务完成后,调用next方法来恢复yield下方代码的执行。以readFile为例,大致流程如下:

// 我们的主任务——显示关键字
// 使用yield暂时中断下方代码执行
// yield后面为promise对象
const showKeyword = function* (filepath) {
    console.log('开始读取');
    let keyword = yield readFile(filepath);
    console.log(`关键字为${filepath}`);
}
// generator的流程控制
let gen = showKeyword();
let res = gen.next();
res.value.then(res => gen.next(res));

在主任务部分,原本readFile异步的部分变成了类似同步的写法,代码变得非常清晰。而在下半部分,则是对于什么时候需要移交回控制权给generator的流程控制。

然而,我们需要手动控制generator的流程,如果能够自动执行generator——在需要的时候自动移交控制权,那么会更加具有实用性。

为此,我们可以使用 co 这个库。它可以是省去我们对于generator流程控制的代码

const co = reuqire('co');
// 我们的主任务——显示关键字
// 使用yield暂时中断下方代码执行
// yield后面为promise对象
const showKeyword = function* (filepath) {
    console.log('开始读取');
    let keyword = yield readFile(filepath);
    console.log(`关键字为${filepath}`);
}
// 使用co
co(showKeyword);

其中,yeild关键字后面需要是functio, promise, generator, arrayobject。可以改写文章一开始的例子:

const co = reuqire('co');
const task = function* (filepath) {
   let keyword = yield readFile(filepath);
   let count = yield queryDB(keyword);
   let data = yield getData(res.length);
   console.log(data);
});
co(task, './sample.txt');

Generator + Promise

Generator 函数与 Promise 结合使用时,可以通过 yield 关键字桥接两者之间的通信。在 Generator 函数中通过 yield 语句返回一个 Promise 对象,在 Promise 对象成功或失败后,再次通过 Generator 函数的 next() 方法恢复生成器的执行。在执行过程中,可以通过 try...catch 捕获错误并进行相应的处理。

通过以上方式,利用 GeneratorPromise 的协作,可以有效解决 JavaScript 中异步编程的问题,提高代码可读性和可维护性。

我们以一个简单的示例来说明 Generator 如何实现异步编程。假设我们有一个包含异步操作的函数 getData(),它返回一个 Promise 对象。

function getData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data received from server');
    }, 2000);
  });
}

接下来,我们创建一个 Generator 函数 run(),在该函数中实现异步操作的流程控制。程序执行到 yield 语句时,run() 函数会暂停执行并将控制权交还给调用者。当 Promise 对象成功返回数据后,再次通过 next() 方法恢复 Generator 的执行。在中间过程中,可以通过 try...catch 捕获错误并进行相应的处理。

function* run() {
  try {
    const data = yield getData();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}
const iterator = run();
const promise = iterator.next().value;
promise.then(data => {
  iterator.next(data);
}).catch(error => {
  iterator.throw(error);
});

首先,我们需要创建一个 Generator 实例 iterator,并通过 iterator.next() 方法启动迭代器,开始执行 run() 函数。此时程序执行到 yield 语句,run() 函数暂停执行并将控制权交还给调用者。

之后,我们获取到 promise 对象,通过 promise 对象的 then() 方法注册回调函数,当数据成功返回时则触发回调函数,恢复 Generator 的执行,并将返回值作为参数传递给 yield 表达式。这样,我们就可以拿到异步操作得到的数据,并进行下一步处理。

当然,在中间过程中如果发生任何错误,我们可以通过 promise 对象的 catch() 方法或 iterator.throw() 方法捕获异常并进行相应的处理。

总之,将 GeneratorPromise 结合使用,利用 yield 语句返回 Promise 对象,再通过 next()、then()、catch()、throw() 等方法实现异步编程的流程控制。

5、async/await

上面的方法虽然都在一定程度上解决了异步编程中回调带来的问题。然而:

  • 事件发布/监听方式模糊了异步方法之间的流程关系;
  • Promise虽然使得多个嵌套的异步调用能够通过链式的API进行操作,但是过多的then也增加了代码的冗余,也对阅读代码中各阶段的异步任务产生了一定干扰;
  • 通过generator虽然能提供较好的语法结构,但是毕竟generatoryield的语境用在这里多少还有些不太贴切。

Async/await 语法是一种在 JavaScript 中处理异步操作的更现代的方式,它提供了一种更简洁的编写异步代码的方式。这是一个例子:

async function fetchData(url) {
  let response = await fetch(url);
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  let data = await response.json();
  console.log(data);
}
fetchData('https://jsonplaceholder.typicode.com/todos/1')
  .catch(error => {
    console.error(error);
  });

在这个例子中,我们定义了一个名为fetchData()的异步函数,使用 await 关键字来等待 fetch() 调用的响应。如果响应是不确定的,我们就抛出一个错误。否则,我们将响应解析为JSON,并将其记录到控制台。

然后我们用一个URL调用 fetchData() 函数,并捕捉任何可能被抛出的错误。

与回调和承诺相比,Async/await 语法为处理异步操作提供了一种更易读、更少出错的方式,并且可以与现代功能如结构化和对象速记符号一起使用。

三、使用 RxJS 进行响应式编程

RxJSReactive Extensions for JavaScript 的缩写,是一种基于可观察对象的异步编程实现方式。RxJS 提供了丰富的操作符和函数,让开发者可以方便地处理异步事件、流数据等场景。

来看一个例子:

const { from } = rxjs;
const { filter, map } = rxjs.operators;
from(fetch('https://jsonplaceholder.typicode.com/todos/1'))
  .pipe(
    filter(response => response.ok),
    map(response => response.json())
  )
.subscribe(data => {
    console.log(data);
}, error => {
    console.error(error);
});

在这个例子中,我们使用 RxJSfrom() 方法,从 fetch() 调用中创建一个可观察变量。然后我们使用 filter()map() 操作符来转换该观察变量,过滤掉非OK响应,并将响应解析为JSON。

然后我们订阅 observable,将数据记录到控制台或捕捉任何可能被抛出的错误。

使用 RxJS 的响应式编程为处理异步数据流提供了一种强大的方式,既可读又可维护,特别是在处理大型复杂数据集时。

参考资料

end~

相关文章
|
7天前
|
前端开发 JavaScript
JavaScript——promise 是解决异步问题的方法嘛
JavaScript——promise 是解决异步问题的方法嘛
15 0
|
5天前
|
JavaScript 前端开发
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
这篇文章是JavaScript基础与实战教程的第一部分,涵盖了JavaScript的基本语法、标识符、数据类型以及如何进行强制类型转换,通过代码示例介绍了JS的输出语句、编写位置和数据类型转换方法。
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
|
4天前
|
JavaScript 前端开发
JavaScript基础&实战 JS中正则表达式的使用
这篇文章介绍了JavaScript中正则表达式的使用,包括正则表达式的创建、匹配模式、字符串匹配、拆分、搜索、匹配和替换等方法,并通过示例代码展示了如何应用这些技术。
JavaScript基础&实战 JS中正则表达式的使用
|
5天前
|
JavaScript 前端开发
JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
这篇文章介绍了JavaScript中的数组、Date对象、Math对象以及包装类(String、Number、Boolean),并详细讲解了数组的创建、方法(如forEach、push、pop、unshift、slice、splice)和遍历操作,以及工厂方法创建对象和原型对象的概念。
JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
|
5天前
|
JavaScript 前端开发
JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
这篇文章介绍了JavaScript中对象的基本概念和操作,包括对象属性和方法的使用、对象字面量的创建、函数的定义和作用域的概念,以及全局作用域和局部作用域的区别和特性。
JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
|
5天前
|
JavaScript 前端开发
JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环
这篇文章讲解了JavaScript中的流程控制语句,包括基本的if条件判断、弹窗提示输入、switch条件分支语句、while和do...while循环以及for循环的使用和示例。
JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环
|
5天前
|
JavaScript 前端开发
JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符
这篇文章详细介绍了JavaScript中的强制类型转换、运算符(包括算术、逻辑、条件、赋值和关系运算符)的使用方法和优先级规则。
JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符
|
6天前
|
存储 JavaScript API
Node.js中的异步API
【8月更文挑战第16天】
13 1
|
24天前
|
数据采集 JavaScript Python
【JS逆向课件:第十三课:异步爬虫】
回调函数就是回头调用的函数
|
19天前
|
SQL 存储 JSON
AlaSQL.js:用SQL解锁JavaScript数据操作的魔法
AlaSQL.js:用SQL解锁JavaScript数据操作的魔法
19 1

热门文章

最新文章