ES6学习笔记20:Generator 函数的异步应用


异步编程对JavaScript非常重要。JavaScript的执行环境是“单线程”的。

传统方法

在ES6之前,异步编程的方法,大概有以下:

  1. 回调函数
  2. 事件监听
  3. 发布/订阅
  4. Promis 对象

基本定义

定义

异步:一个任务不是连续完成的,可以理解为任务被人为分成了两段,先执行第一段,然后执行其他任务,等做好准备了,再回头执行第二段任务。这种不连续执行任务就叫做异步。

同步:连续的执行叫做同步。

回调函数

回调函数就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候就直接调用这个函数。

读取文件进行处理:

var fs=require('fs');

fs.readFile('/etc/password','utf-8',function(err,data){
  if(err) throw err;
  console.log(data);
});

上面的代码中,readFile函数的第三个参数就是回调函数,等到操作系统返回/etc/password这个以后,回调函数才会执行。

Promise

为了避免“回调函数地狱” (回调函数多重嵌套,)Promise将回调函数的嵌套改成了链式调用。

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});

上面的代码中,使用了fs-readfile-promise模块,它的作用就是返回一个PromisereadFile函数。Promise提供then方法加载回调函数,catch方法捕捉执行过程中跑出的错误。

但是Promise的最大问题是代码冗余,原来的任务被Promise包装了胰腺癌,不管什么操作,一眼看去都是一堆then,原来的语义变得不清楚了。

协程

协程:多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程。

它的运行流程:

  1. 协程A开始执行
  2. 协程A执行了一半,进入暂停,执行转移权给协程B.
  3. 一段时间以后,协程B完成自己的任务,交还执行权
  4. 协程A恢复执行
协程的Generator函数实现

Generator 函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(暂停执行)

整个Generator 函数是一个封装的异步任务,或者说异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。

function* gen(x){
  const y = yield x + 2;
  return y;
}

const g = gen(1);
g.next();
g.next();

调用Generator 函数,会返回一个内部指针,即遍历器g.

Generator 函数不会返回结果,返回的是指针对象。调用指针gnext方法,会移动内部指针(即:执行异步任务的第一段),指向第一个遇到yield语句,上面的代码中是执行到x+2为止。

Generator 函数的数据交换和错误处理

Generator 函数的特性

  • Generator 函数可以暂停执行和恢复执行
  • Generator 函数内外的数据交换
  • Generator 函数内外的错误处理机制

上述亮点是Generator函数能封装异步任务的根本原因

方法next()返回值的 value属性,是Generator 函数向外输出数据

方法 next()还可以接受参数,向Generator 函数体内输入数据

function* gen(x){
  const y = yield x + 2;
  return y;
}

const g = gen(1);
g.next(); // { value: 3, done: false }
g.next(2); // { value: 3, done: true}

Generator 函数内部部署错误处理代码,捕获函数体外抛出的错误

function* gen(x){
  let y;
  try {
    y = yield x + 2;
  } catch (e) {
    console.log(e);
  }
  return y;
}

const g = gen(1); // {value: 3, done: false} 
g.next(); // {value: undefined, done: true}
g.throw('Error'); // Error

Generator 函数体外,使用指针对象 throw方法抛出的错误,可以被函数体内的try…catch代码块捕获。从而实现了出错的代码与处理错误的代码,实现了时间和空间上的分离

异步任务的封装
var fetch = require('node-fetch');
function* gen() {
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

上面的代码中,首先执行Generator 函数,获取遍历对象g,然后使用next方法,执行异步任务,由于Fetch模块返回的是一个Promise 对象,因此要用then方法调用下一个```next````方法

Thunk 函数

定义

Thunk 函数是自动执行 Generator 函数的一种方法,将参数放到一个临时函数之中,再将这个临时函数传入函数体,从而实现编译器的“传名调用”,而这个临时函数就叫做Thunk函数.

JavaScript 语言的 Thunk 函数

JavaScript 语言是传值调用的,它的 Thunk 函数的意义是将有多个参数的函数替换为一个只接受回调函数作为参数的单参数函数。

var fs=require('fs');
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName);
readFileThunk(callback);

上面代码中,fs模块的readFile方法是一个多参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个 单参数的函数。这个单参数版本,就叫做 Thunk 函数。

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式

  • 实现一个简单的 Thunk 函数
// ES5版本
var Thunk = function(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};

// ES6版本
const Thunk = function(fn) {
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    }
  };
};

在生产环境的Thunk建议使用 Thunkify模块

备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值