程序执行分为同步和异步,若是程序每执行一步都须要等待上一步完成才能开始,此所谓同步。若是程序在执行一段代码的同时能够去执行另外一段代码,等到这段代码执行完毕再吧结果交给另外一段代码,此所谓异步。
好比咱们须要请求一个网络资源,因为网速比较慢,同步编程就意味着用户必须等待下载处理结束才能继续操做,因此用户体验极为很差;若是采用异步,下载进行中用户继续操做,当下载结束了,告诉用户下载的数据,这样体检就提高了不少。所以异步编程十分重要。
从计算机的角度来说,js 只有一个线程,若是没有异步编程那必定会卡死的!异步编程主要包括如下几种:node
回调函数应该是 js 中十分基础和简单的部分,咱们在定义事件,在计时器等等使用过程当中都使用过:git
fs.readFile('/etc/passwd', function(err, data){ if(err) throw err; console.log(data); });
好比这里的这个文件读取,定义了一个回调函数,在读取文件成功或失败是调用,并不会马上调用。github
如同以前在 Promise 中提到的,当我想不断的读入多个文件,就会遇到回调函数嵌套,书写代码及其的不方便,咱们称之为"回调地狱"。所以 ES6 中引入是了 Promise 解决这个问题。具体表现参看以前的 Promise 部分。可是 Promise 也带来了新的问题,就是代码冗余很严重,一大堆的 then 使得回调的语义不明确。npm
所谓协程就是几个程序交替执行:A开始执行,执行一段时间后 B 执行,执行一段时间后再 A 继续执行,如此反复。编程
function* asyncJob(){ //... var f = yield readFile(fileA); //... }
经过一个 Generator 函数的 yield, 能够将一个协程中断,去执行另外一个协程。咱们能够换一个角度理解 Generator 函数:它是协程在 ES6 中的具体体现。咱们能够简单写一个异步任务的封装:json
var fetch = require('node-fetch'); function* gen(){ var url = 'http://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); //返回的 value 是一个 Promise 对象 result.value.then(function(data){ return data.json; }).then(function(data){ g.next(data); });
在函数传参数时咱们考虑这样一个问题:api
function fun(x){ return x + 5; } var a = 10; fun(a + 10);
这个函数返回25确定没错,可是,咱们传给函数 fun 的参数在编译时到底保留 a + 10
仍是直接传入 20
?显然前者没有事先计算,若是函数内屡次使用这个参数,就会产生屡次计算,影响性能;然后者事先计算了,但若是函数里不使用这个变量就白浪费了性能。采用把参数原封不动的放入一个函数(咱们将这个函数称为 Thunk 函数),用的使用调用该函数的方式。也就是上面的前一种方式传值。因此上面代码等价于:数组
function fun(x){ return x() + 5; } var a = 10; var thunk = function(){ return a + 10}; fun(thunk);
可是 js 不是这样的!js 会把多参数函数给 Thunk 了,以减小参数:promise
var fs = require('fs'); fs.readFile(fileName, callback); var readFileThunk = Thunk(fileName); readFileThunk(callback); var Thunk = function(fileName){ return function(callback){ return fs.readFile(fileName,callback); }; };
这里任何具备回调函数的函数均可以写成这样的 Thunk 函数,方法以下:网络
function Thunk(fn){ return function(){ var args = Array.prototype.slice.call(arguments); return function (callback){ args.push(callback); return fn.apply(this, args); } } } //这样fs.readFile(fileName, callback); 写做以下形式 Thunk(fs.readFile)(fileName)(callback);
关于 Thunk 函数, 能够直接使用 thunkify 模块:
npm install thunkify
使用格式和上面的Thunk(fs.readFile)(fileName)(callback);
一致,但使用过程当中须要注意,其内部加入了检查机制,只容许 callback 被回调一次!
结合 Thunk 函数和协程,咱们能够实现自动流程管理。以前咱们使用 Generator 时候使用 yield
关键字将 cpu 资源释放,执行移出 Generator 函数。能够怎么移回来呢?以前咱们手动调用 Generator 返回的迭代器的 next() 方法,可这毕竟是手动的,如今咱们就利用 Thunk 函数实现一个自动的:
var fs = require('fs'); var thunkify = require('thunkify'); var readFile = thunkify(fs.readFile); var gen = function*(...args){ //args 是文件路径数组 for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; (function run(fn){ var gen = fn(); function next(err, data){ if(err) throw err; var result = gen.next(data); if(result.done) return; //递归直到因此文件读取完成 result.value(next); //递归执行 } next(); })(gen); //以后可使用 run 函数继续读取其余文件操做
若是说 Thunk 能够有现成的库使用,那么这个自动执行的 Generator 函数也有现成的库可使用——co模块(https://github.com/tj/co)。用法与上面相似,不过 co 模块返回一个 Promise 对象。使用方式以下:
var co = require('co'); var fs = require('fs'); var thunkify = require('thunkify'); var readFile = thunkify(fs.readFile); var gen = function*(...args){ //args 是文件路径数组 for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; co(gen).then(function(){ console.log("files loaded"); }).catch(function(err){ console.log("load fail"); });
这里须要注意的是:yield 后面只能跟一个 thunk 函数或 promise 对象。上例中第8行 yield 后面的 readFile 是一个 thunk 函数,因此可使用。
上面已经讲解了 thunk 函数实现自动流程管理,下面使用 Promise 实现一下:
var fs = require('fs'); var readFile = function(fileName){ return new Promise(function(resolve, reject){ fs.readFile(fileName, function(error,data){ if(error) reject(error); resolve(data); }); }); }; var gen = function*(){ for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; (function run(gen){ var g = gen(); var resolve = function(data){ var result = g.next(data); if(result.done) return result.value; result.value.then(resolve); } g.next().value.then(function(data){ resolve(data); }); resolve(); })(gen); //以后可使用 run 函数继续读取其余文件操做
ES7 中提出了 async 函数,可是如今已经能够用了!可这个又是什么呢?其实就是 Generator 函数的改进,咱们上文写过一个这样的 Generator 函数:
var gen = function*(){ for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } };
咱们把它改写成 async 函数:
var asyncReadFiles = async function(){ //* 替换为 async for(var i = 0, len = args.length; i < len; i++){ var r = await readFile(args[i]); //yield 替换为 await console.log(r.toString()); } };
async 函数对 Generator 函数作了一下改进:
var result = asyncReadFiles(fileA, fileB, fileC);
咱们能够实现这样的一个 async 函数:
async function asyncFun(){ //code here } //equal to... function asyncFun(args){ return fun(function*(){ //code here... }); function fun(genF){ return new Promise(function(resolve, reject){ var gen = genF(); function step(nextF){ try{ var next = nextF(); } catch(e) { return reject(e); } if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then(function(data){ step(function(){ return gen.next(data); }); }, function(e){ step(function(){ return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } }
咱们使用 async 函数作点简单的事情:
function timeout(ms){ return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function delay(nap, ...values){ while(1){ try{ await timeout(nap); } catch(e) { console.log(e); } var val = values.shift(); if(val) console.log(val) else break; } } delay(600,1,2,3,4); //每隔 600ms 输出一个数
这里须要注意:应该把后面跟 promise对象的 await 放在一个 try 中,防止其被 rejected。固然上面的 try 语句也能够这样写:
var ms = await timeout(nap).catch((e) => console.log(e));
对于函数参数中的回调函数不建议使用,避免出现不该该的错误
//反例: 会获得错误结果 async function fun(db){ let docs = [{},{},{}]; docs.forEach(async function(doc){ //ReferenceError: Invalid left-hand side in assignment await db.post(doc); }); } //改写, 但依然顺序执行 async function fun(db){ let docs = [{},{},{}]; for(let doc of docs){ await db.post(doc); } } //改写, 并发执行 async function fun(db){ let docs = [{},{},{}]; let promises = docs.map((doc) => db.post(doc)); let result = await Promise.all(promises) console.log(result); } //改写, 并发执行 async function fun(db){ let docs = [{},{},{}]; let promises = docs.map((doc) => db.post(doc)); let result = []; for(let promise of promises){ result.push(await promise); } console.log(result); }
这里咱们实现一个简单的功能,能够直观的比较一下。实现以下功能:
在一个 DOM 元素上绑定一系列动画,每个动画完成才开始下一个,若是某个动画执行失败,返回最后一个执行成功的动画的返回值
function chainAnimationPromise(ele, animations){ var ret = null; //存放上一个动画的返回值 var p = Promise.resolve(); for(let anim of animations){ p = p.then(function(val){ ret = val; return anim(ele); }); } return p.catch(function(e){ /*忽略错误*/ }).then(function(){ return ret; //返回最后一个执行成功的动画的返回值 }); }
function chainAnimationGenerator(ele, animations){ return fun(function*(){ var ret = null; try{ for(let anim of animations){ ret = yield anim(ele); } } catch(e) { /*忽略错误*/ } return ret; }); function fun(genF){ return new Promise(function(resolve, reject){ var gen = genF(); function step(nextF){ try{ var next = nextF(); } catch(e) { return reject(e); } if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then(function(data){ step(function(){ return gen.next(data); }); }, function(e){ step(function(){ return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } }
async function chainAnimationAsync(ele, animations){ var ret = null; try{ for(let anim of animations){ ret = await anim(elem); } } catch(e){ /*忽略错误*/ } return ret; }
console.log(0); setTimeout(function(){ console.log(1) },0); setTimeout(function(){ console.log(2); },1000); var pro = new Promise(function(resolve, reject){ console.log(3); resolve(); }).then(resolve => console.log(4)); console.log(5); setTimeout(function(){ console.log(6) },0); pro.then(resolve => console.log(7)); var pro2 = new Promise(function(resolve, reject){ console.log(8); resolve(10); }).then(resolve => console.log(11)) .then(resolve => console.log(12)) .then(resolve => console.log(13)); console.log(14); // 0 3 5 8 14 4 11 7 12 13 1 6 2