JavaScript 异步编程--Generator函数、async、await

简介:

JavaScript 异步编程–Generator函数

Generator(生成器)是ES6标准引入的新的数据类型,其最大的特点就是可以交出函数的执行的控制权,即:通过yield关键字标明需要暂停的语句,执行时遇到yield语句则返回该语句执行结果,等到调用next函数时(也就是说可以通过控制调用next函数的时机达到控制generator执行的目的)重新回到暂停的地方往下执行,直至generator执行结束。

基本结构

以下是一个典型的generator函数的示例,以”*”标明为generator。

function* gen(x){
  var y = yield x + 2;
  console.log(y);         // undefine
  var yy = yield x + 3;
  console.log(yy);        // 6
  return y;               // 没啥用
}

var g = gen(1);
var r1 = g.next();   
console.log(r1);     // { value: 3, done: false }
var r2 = g.next();
console.log(r2);     // { value: 4, done: false }
var r3 = g.next(6);  
console.log(r3);     // { value: undefined, done: true }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上述代码中,调用gen函数,会返回一个内部指针(即遍历器)g,这是Generator函数和一般函数不同的地方,调用它不会返回结果,而是一个指针对象。调用指针g的next方法,会移动内部指针,指向第一个遇到的yield语句,上例就是执行到x+2为止。换言之,next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象{value: any, done: boolean},表示当前阶段的信息,其中value属性是yield语句后面表达式的值;done属性是一个布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段。next方法输入参数即为yield语句的值,因此生成器gen中y为第二次调用next的输入参数”undefine”,yy为第三次调用next的输入参数6。 
总结:

  • generator返回遍历器,可遍历所有yield
  • yield将生成器内部代码分割成n段,通过调用next方法一段一段执行
  • next方法返回的value属性向外输出数据,next方法通过实参向生成器内部输入数据

思考

如果yield标记的语句是个异步执行的函数func,然后在func回调中调用next,则实现了等待func异步执行的效果—–“func要做的事做完了,才会往下走”,这样就避免了多重回调嵌套(callback hell,回调地狱, 如下所示)

func1(function (res) {
  // do something
  func2(function (res2) {
    // do something
    func3(function (res3) {
      // do something
    })
  })
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Thunk函数

什么是thunk函数?详见Thunk 函数的含义和用法 
简单理解:thunk函数利用闭包可以缓存状态的特性,先传参数,再执行函数,这样就将函数调用过程分成了两步。以下thunkify函数可将普通异步函数转化为thunk函数。

function thunkify(fn) {
  assert('function' == typeof fn, 'function required');
  return function () {
   // arguments为异步函数的参数(不包含回调函数参数)
    var args = new Array(arguments.length);
    var ctx = this;
    for (var i = 0; i < args.length; ++i) {
      args[i] = arguments[i];
    }
    // done为异步函数的回调函数(callback)
    return function (done) {
      var called;

      args.push(function () {
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        // 到这里,异步函数才真正被调用
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Generator执行控制

thunk函数有什么用呢?其一个典型应用就是用于控制generator的执行,见如下示例是为了实现多个文件的顺序读取,实现了同步写法,避免回调嵌套。

const fs = require('fs');
const readFileThunk = thunkify(fs.readFile);

var generator = function* () {
  for (var i = 0; i < arguments.length; i++) {
    console.log('file: %s', arguments[i]);
    // yield 返回thunkify最内部的 function (done){}  函数,此处传入了readFile函数参数,但并没有执行
    var r1 = yield readFileThunk(arguments[i], 'utf8');
    console.log('r1: %s', r1);
  }
}

function rungenerator(generator) {
  //文件名称
  var args = [];
  for (var i = 1; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  //生成generator实例
  var gen = generator.apply(null, args);
  function done(err, data) {
    //执行跳到 generator中去
    var result = gen.next(data);
    if (result.done) { return; }
    // 此处才是真正的调用readFile函数开始读取文件内容,done作为回调, 文件读取完成后,执行gen.next(),
    // 告诉generator继续执行,并通过yield返回下一个thunk函数,开始读取下一个文件,从而达到顺序执行的效果
    result.value(done);
  }
  next();
}
rungenerator(generator, '123.txt', '1234.txt', 'he.txt')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

上述代码中,rungenerator是一个执行generator的函数,具有通用性,封装下就成了co库—-generator函数自动执行的解决方案。

var fs = require('fs');
var co = require('co');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

co(function*(){
    var files=['./text1.txt', './text2.txt', './text3.txt'];

    var p1 = yield readFile(files[0]);
    console.log(files[0] + ' ->' + p1);

    var p2 = yield readFile(files[1]);
    console.log(files[1] + ' ->' + p2);

    var p3 = yield readFile(files[2]);
    console.log(files[2] + ' ->' + p3);

    return 'done';
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

看起来舒服多了。。。

async和await

async和await是ES7中的新语法,实际上是generator函数的语法糖 
Nodejs最新版已经支持了,浏览器不支持的,可以用Babel转下。

var fs = require('fs');

var readFile = function (fileName){
    return new Promise(function (resolve, reject){
        fs.readFile(fileName, function(error, data){
            if (error){
                reject(error);
            }
            else {
                resolve(data);
            }
        });
    });
};

var asyncReadFile = async function (){
    var f1 = await readFile('./text1.txt');
    var f2 = await readFile('./text2.txt');
    console.log(f1.toString());
    console.log(f2.toString());
};

asyncReadFile();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

致谢

主要学习了nullcc的博客《深入解析Javascript异步编程》、无脑的博客《node的 thunkify模块说明》、阮一峰老师的博客《Thunk 函数的含义和用法》,由衷地感谢以上作者!!!!!

原文发布时间:2018-6-19

原文作者:阳光七十米

本文来源csdn博客如需转载请紧急联系作者


相关文章
|
23天前
|
JavaScript
变量和函数提升(js的问题)
变量和函数提升(js的问题)
|
2天前
|
JavaScript 前端开发
js开发:请解释什么是ES6的Generator函数,以及它的用途。
ES6的Generator函数是暂停/恢复功能的特殊函数,利用yield返回多个值,适用于异步编程和流处理,解决了回调地狱问题。例如,一个简单的Generator函数可以这样表示: ```javascript function* generator() { yield &#39;Hello&#39;; yield &#39;World&#39;; } ``` 创建实例后,通过`.next()`逐次输出&quot;Hello&quot;和&quot;World&quot;,展示其暂停和恢复的特性。
13 0
|
3天前
|
JavaScript 前端开发
js开发:请解释同步和异步编程的区别。
同步编程按顺序执行,易阻塞;异步编程不阻塞,提高效率。同步适合简单操作,异步适合并发场景。示例展示了JavaScript中同步和异步函数的使用。
14 0
|
4天前
|
前端开发 JavaScript 编译器
深入解析JavaScript中的异步编程:Promises与async/await的使用与原理
【4月更文挑战第22天】本文深入解析JavaScript异步编程,重点讨论Promises和async/await。Promises用于管理异步操作,有pending、fulfilled和rejected三种状态。通过.then()和.catch()处理结果,但可能导致回调地狱。async/await是ES2017的语法糖,使异步编程更直观,类似同步代码,通过事件循环和微任务队列实现。两者各有优势,适用于不同场景,能有效提升代码可读性和维护性。
|
9天前
|
缓存 JavaScript 前端开发
js的入口函数,入口函数的作用
js的入口函数,入口函数的作用
15 4
|
前端开发 JavaScript
JavaScript异步编程:Generator与Async
从Promise开始,JavaScript就在引入新功能,来帮助更简单的方法来处理异步编程,帮助我们远离回调地狱。Promise是下边要讲的Generator/yield与async/await的基础,希望你已经提前了解了它。
1318 0
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0
|
2月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
77 0
|
3天前
|
JavaScript 前端开发 测试技术
学习JavaScript
【4月更文挑战第23天】学习JavaScript
11 1
|
11天前
|
JavaScript 前端开发 应用服务中间件
node.js之第一天学习
node.js之第一天学习