上篇文章咱们讲了下JS异步编程的相关知识,好比什么是异步,为何要使用异步编程以及在浏览器中JS如何实现异步的。
最后咱们捎带讲了几种JS异步编程模式(回调,事件和发布/订阅模式),这篇咱们继续去深刻了解下其余的几种异步编程模式。html
其实这几个函数用来解决,异步中 回调函数嵌套问题 (callback hell) 回调地狱编程
Promise是ES6推出的一种异步编程的解决方案。其实在ES6以前,不少异步的工具库就已经实现了各类相似的解决方案,而ES6将其写进了语言标准,统一了用法。Promise解决了回调等解决方案嵌套的问题而且使代码更加易读,有种在写同步方法的既视感。json
咱们先来简单了解下ES6中Promise的用法promise
var p = new Promise(function async(resolve, reject){ // 这里是你的异步操做 setTimeout(function(){ if(true){ resolve(val); }else{ reject(error); } }, 1000) }) p.then(function(val){ console.log('resolve'); }, function(){ console.log('reject'); })
首先,ES6规定Promise是个构造函数,其接受一个函数做为参数如上面代码中的async
函数,此函数有两个参数,resolve、reject分别对应成功失败两种状态,咱们能够选择在不一样时候执行resolve或者reject去触发下一个动做,执行then方法里的函数。浏览器
咱们能够简单对比下回调的写法和promise的写法的不一样异步
对于传统回调写法来讲,通常会写成这样async
asyncFn1(function () { asyncFn2(function() { asyncFn3(function() { // xxxxx }); }); });
或者咱们将各个回调函数拆出来独立来写以减小耦合,像是这样:异步编程
function asyncFn1(callback) { return function() { console.log('asyncFn1 run'); setTimeout(function(){ callback(); }, 1000); } } function asyncFn2(callback) { return function(){ console.log('asyncFn2 run'); setTimeout(function(){ callback(); }, 1000); } } function normalFn3() { console.log('normalFn3 run'); } asyncFn1(asyncFn2(normalFn3))()
最后咱们看下Promise的写法函数
function asyncFn1() { console.log('asyncFn1 run'); return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(); }, 1000) }) } function asyncFn2() { console.log('asyncFn2 run'); return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(); }, 1000) }) } function normalFn3() { console.log('normalFn3 run'); } asyncFn1().then(asyncFn2).then(normalFn3);
这样来看不管是第一种仍是第二种写法,都会让人感到不是很直观,而Promise的写法更加直观和语义化。工具
Generator函数也是ES6提供的一种特殊的函数,其语法行为与传统函数彻底不一样。
咱们先直观看个Generator实际的用法
function* oneGenerator() { yield 'Learn'; yield 'In'; return 'Pro'; } var g = oneGenerator(); g.next(); // {value: "Learn", done: false} g.next(); // {value: "In", done: false} g.next(); // {value: "Pro", done: true}
Generator函数是一种特殊的函数,他有这么几个特色:
声明时须要在function
后面加上*
,而且配合函数里面yield
关键字来使用。
在执行Generator函数的时候,其会返回一个Iterator遍历器对象,经过其next方法,将Generator函数体内的代码以yield为界分步执行
具体来讲当执行Generator函数时,函数并不会执行,而是须要调用Iterator遍历器对象的next方法,这时程序才会执行从头或者上一个yield以后
到 到下一个yield或者return或者函数体尾部
之间的代码,而且将yield后面的值,包装成json对象返回。就像上面的例子中的{value: xxx, done: xxx}
。
value取的yield或者return后面的值,不然就是undefined,done的值若是碰到return或者执行完成则返回true,不然返回false。
咱们知道了简单的Generator函数的用法之后,咱们来看下如何使用Generator函数进行异步编程。
首先咱们先来看下使用Generator函数能达到怎样的效果。
// 使用Generator函数进行异步编程 function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn3(); } // 咱们来对比一下Promise asyncFn1().then(asyncFn2).then(normalFn3);
咱们能够看出使用Generator函数进行异步编程更像是在写同步任务,对比Promise少了不少次then方法的调用。
好,那么接下来咱们就来看下如何实际使用Generator函数进行异步编程。
这里我要特别说明一下,事实上Generator函数不像Promise同样是专门用来解决异步处理而产生的,人们只是使用其特性来产出了一套异步的解决方案,因此使用Generator并不像使用Promise同样有一种开箱即用的感受。其更像是在Promise或者回调这类的解决方案之上又封装了一层,让你能够像上面例子里同样去那么写。
咱们仍是具体来看下上面的例子,咱们知道单写一个Generator是不能运行的对吧,咱们须要执行他而且使用next方法来让他分步执行,那么何时去调用next呢?答案就是咱们须要在异步完成时去调用next。咱们来按照这个思路补全上面的例子。
var g; function asyncFn() { setTimeout(function(){ g.next(); }, 1000) } function normalFn() { console.log('normalFn run'); } function* oneGenerator() { yield asyncFn(); return normalFn(); } g = oneGenerator(); g.next(); // 这里在我调用next方法的时候执行了asyncFn函数 // 而后咱们的但愿是在异步完成时自动去再调用g.next()来进行下面的操做,因此咱们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。 // 但其实这样是比较奇怪的,由于当我定义asyncFn的时候实际上是不知道oneGenerator执行后叫什么名儿的,即便咱们提早约定叫g,但这样asyncFn就太过于耦合了,不只写法很奇怪并且耦合太大不利于扩展和重用。反正总而言之这种写法很很差。
那么怎么解决呢,咱们须要本身写个方法,能自动运行Generator函数,这种方法很简单在社区里有不少,最著名的就是大神TJ写的co模块,有兴趣的同窗能够看下其源码实现。这里咱们简单造个轮子:
// 若是咱们想要去在异步执行完成时自动调用next就须要有一个钩子,回调函数的callback或者Promise的then。 function autoGenerator(generator){ var g = generator(); function next(){ var res = g.next(); // {value: xxx, done: xxx} if (res.done) { return res.value; } if(typeof res.value === 'function'){ // 认为是回调 res.value(next); }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){ // 认为是promise res.value.then(function(){ next(); }) }else{ next(); } } next(); } // ---- function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve(); }, 1000) }) } function asyncFn2() { console.log('asyncFn2'); return function(callback){ setTimeout(function(){ callback(); }, 1000); } } function normalFn() { console.log('normalFn'); } function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn(); } autoGenerator(oneGenerator);
这个方法咱们简单实现了最核心的部分,有些判断可能并不严谨,但你们理解这个思路就能够了。有了这个方法,咱们才能够方便的使用Generator函数进行异步编程。
若是你学会了Generator函数,对于Async函数就会很容易上手。你能够简单把Async函数理解成就是Generator函数+执行器。咱们就直接上实例好了
function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve('123'); }, 2000) }) } function asyncFn2() { console.log('asyncFn2'); return new Promise(function(resolve){ setTimeout(function(){ resolve('456'); }, 2000) }) } async function asyncFn () { var a = await asyncFn1(); var b = await asyncFn2(); console.log(a,b) } asyncFn(); // asyncFn1 // asyncFn2 // 123,456
固然async里实现的执行器确定是跟我们上面简单实现的有所不一样,因此在用法上也会有些注意的点
首先async函数的返回值是一个Promise对象,不像是generator函数返回的是Iterator遍历器对象,因此async函数执行后能够继续使用then等方法来继续进行下面的逻辑
await后面通常跟Promise对象,async函数执行时,遇到await后,等待后面的Promise对象的状态从pending变成resolve的后,将resolve的参数返回并自动往下执行直到下一个await或者结束
await后面也能够跟一个async函数进行嵌套使用。
对于异步来讲,还有不少的知识点咱们没有讲到,好比异常处理,多异步并行执行等等,这篇和上篇文章主要仍是但愿你们对异步编程有个直观的了解,清楚各类解决方案之间的区别和优劣。
转 : https://www.cnblogs.com/learninpro/p/9271813.html