异步编程对JavaScript来讲很是重要,由于JavaScript的语言环境是单线程的,若是没有异步编程将变得很是可怕,估计根本没法使用。这篇文章就来总结一下从最原始的回调函数到如今的ES六、ES7的新方法。jquery
文章并不会具体介绍每种方法的原理,若是不是特别懂须要详细了解的同窗能够看阮一峰的ES6入门。阮大大介绍得很是具体,从原理到用法。es6
单线程就是指进程中只有一个线程。单线程执行程序时,按照代码的顺序,上一个任务完成后才会执行下一个任务。同一个时间只作一件事情。ajax
JavaScript的主要做用就是操做DOM,若是两段JS同时操做一个DOM,会引发渲染的冲突。因此JavaScript只能是单线程的。
HTML5中提出的Web Worker,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。编程
同步是指任务一件一件的按顺序完成,上一件没有完成就没法作下一件;而异步则是指,开始作一件事以后就放在那儿等待结果,不须要守着,继续作下一件事便可。json
异步能够解决JavaScript单线程效率低、同一事件只能作一件事情的问题。api
console.log(1); setTimeOut(()=>{ console.log(2); },1000) console.log(3);
这段代码首先会打印1,而后打印3,过1000ms之后打印2。promise
然而这段代码内部是如何运行的呢,打印1和打印3的命令是同步命令,因此直接按顺序放到主进程中执行,setTimeOut里的是一个异步命令,在1000ms之后会被放入异步队列中。而主进程会经过事件循环(event loop)不断地从异步队列中取出命令,而后执行。当主进程在1000ms之后查询到了打印2的命令时,便把这个函数拿到主进程中执行。异步
这里所有用ajax连续调用例子,接口是豆瓣的真实接口,能够获得具体的数据,但有限制每小时150次。async
这是最原始的一种异步解决方法。回调函数,就是指一件事作完之后,拿到结果后要作的事情。异步编程
var urlBase = 'https://api.douban.com/'; var start = 0,count = 5; $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success: function(data){ console.log(data); start+=count; $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ console.log(data); start+=count; $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ console.log(data); } }) } }) } })
这是用jquery的ajax方法调用的豆瓣某我的的收藏的图书,start和count是豆瓣提供的接口参数,start表明从哪一条数据开始获取,count表明一共获取多少条数据。
从上面的代码能够看到多个回调函数的嵌套,若是须要调用得越多,回调也堆积得越多,多了之后代码就很难维护,时间久了本身也要花好久才能看懂代码。
将每一次回调的方法封装成函数,代码量会减小不少。
var urlBase = 'https://api.douban.com/'; var start = 0,count = 5; function ajax(start,count,cb){ $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ console.log(data); start+=count; cb && cb(start); } }) } ajax(start,count,function(start){ ajax(start,count,function(start){ ajax(start,count) }) });
可是这样依然没有解决“回调地狱”的问题,当每次回调的逻辑操做变得愈来愈多的时候,代码依然难以维护。
Promise对象是ES6提出的一种对异步编程的解决方案,但它不是新的语法,而是一种新的写法,容许将回调函数的嵌套改为链式调用。
虽说Promise是ES6提出的标准,但其实jQuery在1.5版本之后就提出了相似的东西,叫作deferred对象。具体学习能够看jQuery的deferred对象详解。
const urlBase = 'https://api.douban.com/'; let start = 0,count = 5; function ajax(start,count){ let dtd = $.Deferred(); $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ start+=count; dtd.resolve(data,start); }, error:function(err){ dtd.reject(err); } }) return dtd; } ajax(start,count).then((data1,start) => { console.log(data1); return ajax(start,count); }).then((data2,start) => { console.log(data2); return ajax(start,count); }).then((data3,start) => { console.log(data3); }).catch((err) => { console.log('这里出错啦'); })
从这段代码能够看出来,写法和promise很是类似了,能够猜想promise就是从deferred演化而来的。
一样的功能实现能够改为如下写法:
const urlBase = 'https://api.douban.com/'; let start = 0,count = 5; function ajax(start,count){ return new Promise(function(resolve,reject){ $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ start+=count; resolve(data,start); }, error:function(err){ reject(err); } }) }) } ajax(start,count).then((data1,start) => { console.log(data1); return ajax(start,count); }).then((data2,start) => { console.log(data2); return ajax(start,count); }).then((data3,start) => { console.log(data3); }).catch((err) => { console.log('这里出错啦'); })
Promise使用.then方法解决了回调的问题,但代码依然冗余,且语义不强,放眼望去全是.then方法,很难找出须要修改的地方。
Generator函数也是ES6中提出的异步编程解决方法,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。最大特色就是能够交出函数的执行权(即暂停执行)。
异步操做须要暂停的地方,都用yield语句注明。
const urlBase = 'https://api.douban.com/'; let start = 0,count = 5; function ajax(start,count){ return new Promise(function(resolve,reject){ $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ start+=count; resolve(data); }, error:function(err){ reject(err); } }) }) } let gen = function*(){ yield ajax(start,count); start+=count; yield ajax(start,count); start+=count; yield ajax(start,count); } let g = gen(); g.next().value.then((data1) => { console.log(data1); g.next().value.then((data2) => { console.log(data2); g.next().value.then((data3) => { console.log(data3); }) }) })
这样在gen函数内三个ajax请求就看起来很是像同步的写法了,可是执行的过程并不清晰,且须要手动.next来执行下一个操做。这并非咱们想要的完美异步方案。
async函数是ES7提出的一种异步解决方案,它与generator并没有大的不一样,并且能够说它就是generator的一种语法糖。它的语法只是把generator函数里的*换成了async,yield换成了await,但它同时有几个优势。
(1)内置执行器。这表示它不须要不停的next来使程序继续向下进行。
(2)更好的语义。async表明异步,await表明等待。
(3)更广的适用性。await命令后面能够跟Promise对象,也能够是原始类型的值。
(4)返回的是Promise。
const urlBase = 'https://api.douban.com/'; let start = 0,count = 5; function ajax(start,count){ return new Promise(function(resolve,reject){ $.ajax({ url: urlBase+'v2/book/user/1219073/collections', type: 'GET', dataType: 'jsonp', data:{ start:start, count:count }, success:function(data){ start+=count; resolve(data); }, error:function(err){ reject(err); } }) }) } async function getData(){ let data = null; try{ for(let i = 0;i < 3;i++){ data = await ajax(start,count); console.log(data); start+=count; } } catch(err){ console.log(err); } } getData();
用async函数改写以后语义清晰,代码量也减小了,而且内部自带执行器,感受很符合想象中的异步解决方法。
到此就把几种常见的异步回调方法介绍完了,我我的感受用async+promise是最好的办法。固然为了更加深入的理解这些异步解决办法,必定要多多的用到项目中,多用才会多理解。