当前位置:K88软件开发文章中心编程语言JavaScriptECMAScript → 文章内容

ECMAScript 6 异步操作和Async函数

减小字体 增大字体 作者:佚名  来源:网上搜集  发布时间:2019-1-15 15:38:39

nsole.log.bind(console);ft(1, 2)(print);// 3上面代码中,由于thunkify只允许回调函数执行一次,所以只输出一行结果。Generator 函数的流程管理你可能会问, Thunk函数有什么用?回答是以前确实没什么用,但是ES6有了Generator函数,Thunk函数现在可以用于Generator函数的自动流程管理。Generator函数可以自动执行。function* gen() { // ...}var g = gen();var res = g.next();while(!res.done){ console.log(res.value); res = g.next();}上面代码中,Generator函数gen会自动执行完所有步骤。但是,这不适合异步操作。如果必须保证前一步执行完,才能执行后一步,上面的自动执行就不可行。这时,Thunk函数就能派上用处。以读取文件为例。下面的Generator函数封装了两个异步操作。var fs = require('fs');var thunkify = require('thunkify');var readFile = thunkify(fs.readFile);var gen = function* (){ var r1 = yield readFile('/etc/fstab'); console.log(r1.toString()); var r2 = yield readFile('/etc/shells'); console.log(r2.toString());};上面代码中,yield命令用于将程序的执行权移出Generator函数,那么就需要一种方法,将执行权再交还给Generator函数。这种方法就是Thunk函数,因为它可以在回调函数里,将执行权交还给Generator函数。为了便于理解,我们先看如何手动执行上面这个Generator函数。var g = gen();var r1 = g.next();r1.value(function(err, data){ if (err) throw err; var r2 = g.next(data); r2.value(function(err, data){ if (err) throw err; g.next(data); });});上面代码中,变量g是Generator函数的内部指针,表示目前执行到哪一步。next方法负责将指针移动到下一步,并返回该步的信息(value属性和done属性)。仔细查看上面的代码,可以发现Generator函数的执行过程,其实是将同一个回调函数,反复传入next方法的value属性。这使得我们可以用递归来自动完成这个过程。Thunk函数的自动流程管理Thunk函数真正的威力,在于可以自动执行Generator函数。下面就是一个基于Thunk函数的Generator执行器。function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next();}function* g() { // ...}run(g);上面代码的run函数,就是一个Generator函数的自动执行器。内部的next函数就是Thunk的回调函数。next函数先将指针移到Generator函数的下一步(gen.next方法),然后判断Generator函数是否结束(result.done属性),如果没结束,就将next函数再传入Thunk函数(result.value属性),否则就直接退出。有了这个执行器,执行Generator函数方便多了。不管内部有多少个异步操作,直接把Generator函数传入run函数即可。当然,前提是每一个异步操作,都要是Thunk函数,也就是说,跟在yield命令后面的必须是Thunk函数。var g = function* (){ var f1 = yield readFile('fileA'); var f2 = yield readFile('fileB'); // ... var fn = yield readFile('fileN');};run(g);上面代码中,函数g封装了n个异步的读取文件操作,只要执行run函数,这些操作就会自动完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行。Thunk函数并不是Generator函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制Generator函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。co模块基本用法co模块是著名程序员TJ Holowaychuk于2013年6月发布的一个小工具,用于Generator函数的自动执行。比如,有一个Generator函数,用于依次读取两个文件。var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString());};co模块可以让你不用编写Generator函数的执行器。var co = require('co');co(gen);上面代码中,Generator函数只要传入co函数,就会自动执行。co函数返回一个Promise对象,因此可以用then方法添加回调函数。co(gen).then(function (){ console.log('Generator 函数执行完成');});上面代码中,等到Generator函数执行结束,就会输出一行提示。co模块的原理为什么co可以自动执行Generator函数?前面说过,Generator就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。两种方法可以做到这一点。(1)回调函数。将异步操作包装成Thunk函数,在回调函数里面交回执行权。(2)Promise 对象。将异步操作包装成Promise对象,用then方法交回执行权。co模块其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。使用co的前提条件是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象。上一节已经介绍了基于Thunk函数的自动执行器。下面来看,基于Promise对象的自动执行器。这是理解co模块必须的。基于Promise对象的自动执行还是沿用上面的例子。首先,把fs模块的readFile方法包装成一个Promise对象。var fs = require('fs');var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); });};var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString());};然后,手动执行上面的Generator函数。var g = gen();g.next().value.then(function(data){ g.next(data).value.then(function(data){ g.next(data); });});手动执行其实就是用then方法,层层添加回调函数。理解了这一点,就可以写出一个自动执行器。function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next();}run(gen);上面代码中,

上一页  [1] [2] [3] [4] [5] [6]  下一页


ECMAScript 6 异步操作和Async函数