异步编程之 Generator 领略魅力

发布于 2021-11-06 15:36:50 字数 3609 浏览 1186 评论 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技术交流群

发布评论

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

关于作者

文章
评论
600 人气
更多

推荐作者

夢野间

文章 0 评论 0

doggiejohn

文章 0 评论 0

就此别过

文章 0 评论 0

初见终念

文章 0 评论 0

qq_rvKjBH

文章 0 评论 0

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