在咱们平常编码中,须要异步的场景不少,好比读取文件内容、获取远程数据、发送数据到服务端等。由于浏览器环境里Javascript
是单线程的,因此异步编程在前端领域尤其重要。前端
所谓异步,是指当一个过程调用发出后,调用者不能马上获得结果。实际处理这个调用的过程在完成后,经过状态、通知或者回调来通知调用者。git
好比咱们写这篇文字时点击发布按钮,咱们并不能立刻获得文章发布成功或者失败。等待服务器处理,这段时间咱们能够作其余的事情,当服务器处理完成后,通知咱们是否发布成功。es6
所谓同步,是指当一个过程调用发出后,必须等待这个过程处理完成后,再处理其余事情。即堵塞执行。github
在es6
以前,咱们实现异步有4种方法,回调、事件、发布订阅和promise方式。web
function dealTask(param, callback) { // Deal with some time consuming tasks. // ... Object.prototype.toString.call(callback) === '[object Function]' ? callback() : null; } dealTask({ id: 1 }, function() { console.log('... I am in the callback...'); })
回调的方式来实现异步其实就是把须要在当前任务完成后执行的函数当成参数传入,完成任务后执行便可。编程
function dealTask(param) { // Deal with some time consuming tasks. // ... events.trigger('dealTaskFinish') } events.on('dealTaskFinish', function() { console.log('...I am in the end...'); })
经过事件来实现回调,好处是方便实用,跨模块传递数据。坏处是,事件用的多了后业务逻辑混乱,不知道哪里注册过哪里监听过。api
另外须要注意的是在web component
场景下,mount
后注册过的事件须要在unmount
释放,否则会致使内存泄露。promise
发布订阅的简单例子是,一个开关,同时并联几个灯泡(在不一样房间),触发的时候,几个灯泡都会获得指令,而后执行发光的行为。浏览器
// 使用pubsubz实现 var testSubscriber = function(data ){ console.log(data ); }; var testSubscription = pubsubz.subscribe( 'example', testSubscriber ); pubsubz.publish( 'example', 'hello' );
订阅发布与的性质与"事件监听"相似,不一样的是,咱们能够经过查看"消息中心",了解存在多少信号、每一个信号有多少订阅者,从而监控程序的运行。服务器
function helloWorld (ready) { return new Promise(function (resolve, reject) { if (ready) { resolve("Hello World!") } else { reject("Good bye!") } }) } helloWorld(true).then(function (message) { console.log(message) }, function (error) { console.log(error) })
Promises对象是CommonJS工做组提出的一种规范,是对异步编程的一种统一,其实也就是语法糖,可阅读性变强了而已。
在ES6出来之后,咱们的异步方式也发生了改变。
Generator函数是协程在ES6的实现,最大特色就是能够交出函数的执行权(即暂停执行)。
Generator函数能够暂停执行和恢复执行,这是它能封装异步任务的根本缘由。除此以外,它还有两个特性,使它能够做为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
function* Foo(x) { yield x + 1; var y = yield null; return x + y; } var foo = Foo(5); foo.next(); // { value: 6, done: false } foo.next(); // { value: null, done: false } foo.next(8); // { value: 13, done: true }
next方法返回值的value属性,是Generator函数向外输出数据;next方法还能够接受参数,这是向Generator函数体内输入数据。
yield命令用于将程序的执行权移出Generator函数,那么就须要一种方法,将执行权再交还给Generator函数
上面的方式,是咱们手动调用Generator函数执行,可是当咱们的须要执行next方法不少时,就须要Generator函数自动执行了。
Generator函数自动执行的意思是,经过必定的方法来自动执行next方法,好比:
function autoRunGen(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(); }
co模块是TJ开发的一个小工具,用于Generator函数的自动执行。他的主要思想和上面的代码片断相似。
使用co的前提条件是,Generator函数的yield命令后面,只能是Promise对象。
co(function *() { var data = yield $.get('/api/data'); console.log(data); var user = yield $.get('/api/user'); console.log(user); var products = yield $.get('/api/products'); console.log(products); });
co模块使得咱们能够像写同步代码同样,写异步代码。
async函数仅仅是Generator函数的语法糖。
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* (){ var f1 = yield readFile('/etc/a.js'); var f2 = yield readFile('/etc/b.js'); console.log(f1.toString()); console.log(f2.toString()); };
使用async/await
方式:
var asyncReadFile = async function (){ var f1 = await readFile('/etc/a.js'); var f2 = await readFile('/etc/b.js'); console.log(f1.toString()); console.log(f2.toString()); };
async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已。
不一样的是,Generator执行须要手动执行,而async函数能够自动执行,像写同步同样写异步。Generator返回Iterator对象,async函数返回Promise对象。