【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~

相关文章
|
13天前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
5天前
|
JSON JavaScript 前端开发
使用JavaScript和Node.js构建简单的RESTful API
使用JavaScript和Node.js构建简单的RESTful API
|
1月前
|
前端开发 JavaScript 开发者
JS 异步解决方案的发展历程以及优缺点
本文介绍了JS异步解决方案的发展历程,从回调函数到Promise,再到Async/Await,每种方案的优缺点及应用场景,帮助开发者更好地理解和选择合适的异步处理方式。
|
1月前
|
人工智能 JavaScript 前端开发
使用Node.js模拟执行JavaScript
使用Node.js模拟执行JavaScript
|
1月前
|
消息中间件 JavaScript 前端开发
用于全栈数据流的 JavaScript、Node.js 和 Apache Kafka
用于全栈数据流的 JavaScript、Node.js 和 Apache Kafka
45 1
|
1月前
|
JavaScript 前端开发
电话号码正则表达式 代码 javascript+html,JS正则表达式判断11位手机号码
电话号码正则表达式 代码 javascript+html,JS正则表达式判断11位手机号码
105 1
|
1月前
|
Web App开发 JavaScript 前端开发
Node.js:JavaScript世界的全能工具
Node.js:JavaScript世界的全能工具
|
1月前
|
JSON JavaScript 前端开发
使用JavaScript和Node.js构建简单的RESTful API服务器
【10月更文挑战第12天】使用JavaScript和Node.js构建简单的RESTful API服务器
17 0
|
1月前
|
移动开发 JavaScript 前端开发
【JavaScript】JS执行机制--同步与异步
【JavaScript】JS执行机制--同步与异步
21 0
|
存储 JavaScript 前端开发
JavaScript与PHP中正则
有个在线调试正则的工具,点击查看工具。下面的所有示例代码,都可以在codepen上查看到。
JavaScript与PHP中正则