异步编程之 Generator 领略魅力

发布于 2021-11-06 15:36:50 字数 3609 浏览 1183 评论 0

为何使用 Generator

回顾一下我们之前学习的 promise。我们巧妙利用了 promise/deferred 模式,用链式结构代替了嵌套回调的结构,大大缓解了回调地狱。我们再来看看之前我们举的那个异步串行队列的例子吧!假设我们有一个 hello.txt,里面存了一个 JSON 文件的文件名,我们需要得到 JSON 文件的 message 属性的值。步骤如下:

  1. 读取 hello.txt 文件
  2. 得到JSON文件名,再次读取文件
  3. 得到JSON数据后,进行JSON解析
  4. 获得JSON的message属性

Promise 链式调用

这个例子我们之前也是举过非常多次的,我们尝试使用Promise链式结构完成:

//这里的readFile已经是promise化的异步API
readFile('hello.txt', 'utf-8')
    .then(function(filename){
        return readFile(filename, 'utf-8');
    })
    .then(JSON.parse)
    .then(function(data){
        console.log(data.message);
    })
    .catch(function(err){
        console.error(err.message);
    });

这样一看下来,promise好像并没有多大问题,思维是线性的,而且错误处理也很友好。我们只需要把上一层执行后的结果通过then()传到下一步执行即可。嗯,但不得不说被链式结构束缚后,我们并没有得到一种酣畅淋漓的编程体验。

同步API

我们要写的爽,当然是要将异步编程得到同步编程的体验,这样我们直接使用同步API看一下是怎样的:

var filename = fs.readFileSync('hello.txt', 'utf-8');
var json = fs.readFileSync(filename, 'utf-8');

console.log(JSON.parse(json).message);

同步的写法清晰明了,而且更符合我们以往的编程习惯。但是同步API阻塞代码这个弊病会在Javascript的单线程执行中非常明显。我们到底有没有一种既可以非常接近同步编程的写法,又可以异步不阻塞代码执行呢?既然问出这种问题,答案当然是有的,就是今天的主角:Generator

Generator 使用 co 写法

Generator,顾名思义是一个构造器,它本身是用来生成迭代器的。它是ES6的新东西,所以你为了使用它,需要在node中开启harmony模式才能体验到它。

$ node --harmony

基于Generator,TJ大神做了一个co库。co在最新的版本里,结合Generator和Promise改善了异步编程的体验,也就是我们之前说的:既可以同步,又不会阻塞

还是一样的例子,我们结合promise的代码和同步API的代码对比看看:

co(function* (){
    var filename = yield readFile('hello.txt', 'utf-8');
    var json = yield readFile(filename, 'utf-8');
    return JSON.parse(json).message;
}).then(console.log, console.error);

非常像有没有,我们不再需要将每一次异步的结果都放在then()中进行处理,我们可以通过类似于同步的写法调用Promise异步API,大大提升编程体验。最后co()返回了一个promise对象,提供我们做最后的数据处理和错误处理。我们从同步API转到co,仅仅需要做到以下几点:

  • co里面传的函数标识符需要加上_号,function_。这也就是Generator函数
  • 调用promise异步API之前,都要加上 yield 标识符
  • 将需要做最后处理的数据 return 出来,在 then() 中进行处理即可

预习 Generator

我们在举完异步串行的例子后,这次的文章就接近尾声了。最后我们可以大致了解一下co到底是如何运作的呢?我们会在接下来的文章进行深究,这一次就简单说一说,你当作预习就可以了:

Generator 相关

  1. Generator生成迭代器后,等待迭代器的 next() 指令启动。
  2. 启动迭代器后,代码会运行到yield处停止。并返回一个 {value: AnyType, done: Boolean} 对象,value 是这次执行的结果,done 是迭代是否结束。并等待下一次的 next() 指令。
  3. next() 再次启动后。若 done 属性不为 true,则可以继续从上一次停止的地方继续迭代。
  4. 一直重复 2,3 步骤,直到 done 为 true。

co 相关

  1. co内部的迭代器对象是被封装成Promise的。
  2. yield 后面跟的必须是一个promise化的异步API,所以 next() 得到的结果是一个promise对象。
  3. 若迭代没有结束,则 co 会自动为该异步 promise 对象的 resolve 中,增添一个 next()。通过前面的异步执行完回调后,再调用next(),使迭代器的代码不断向前执行。
  4. 若迭代结束,则直接调用整个迭代器对象的 resolve

总结

或许现在大家看的是一知半解,或许很兴奋想知道更多相关的。若仅仅是想学会用 co,我想上面的大概已经足够你看了。但是想更深入,你必须先弄懂promise的原理和Generator的相关特性。最后使用 co 库一定会得心应手。

接下来,我会先讲一些关于 Generator 的相关特性,再配合之前说过的 promise,深入到 co 的源码学习中。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
598 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文