异步编程
发现自己还没写过关于异步的总结,由于在实际开发过程中,使用的异步操作是比较多的,所以回过头来总结一下。
传统的callback
众所周知,JS是单线程的,如果都采用同步来写I/O请求的话,因为I/O所花费的时间会比较长,所以,有可能阻塞网页,所以都是用异步来写I/O,请求I/O后这个调用会立刻返回,但是没有返回结果,当I/O请求结束后,会通过回调函数来通知调用方。
JS的异步编程,依靠的是event-loop
,即事件循环。
参考下面这个视频
举个简单的回调函数的例子
window对象监听load
事件,当事件发生,执行回调函数,打印文字
1 | window.addEventListener('load', function() { |
传统的Ajax也是使用回调函数的一个例子。
传说中的callback hell
,就是说回调函数里面再嵌套回调函数,当嵌套的层数多了,代码的可读性和可维护性也就降低了。
Promise
为了解决这种回调函数存在的问题,社区就根据Promise/A+规范
提出并实现了Promise
。
Promise
,字面意思,承诺,可以理解为一个容器,里面承载着未来必定会发生的某个动作的结果
,无论成功或者失败。
对于我自己来说,ES6里用得比较多的就是Promise了。
Promise的写法,感官上就是把嵌套回调函数由横向变成纵向。
1 | // 一个HTTP请求 |
Generator
构造器函数也是ES6提出的,Generator函数可以看成一个状态机,内部的yield
表达式定义不同的状态。Generator总会返回一个遍历器对象。使用next
方法转换成下一个状态。next
方法也可以看成将执行权移交给下一个协程。
如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用yield表示式交换控制权。 (阮一峰的《ECMAScript 6 入门》)
HTTP请求的generator函数
写法
1 | function* getUser() { |
因为generator函数不会自动执行,所以需要写执行函数,或者是利用已有的工具,如co模块
,传入co函数的是一个generator函数
,而不是遍历器对象
1 | var co = require('co') |
co模块的原理,就是自动归还执行权
,前面手动执行generator函数,都是调用next
方法来归还执行权,有两种方法来让某个异步任务完成后自动归还执行权。一是利用Thunk函数
,二是利用Promise
Thunk函数
学习编译原理的时候有接触到求值策略
,参数的传值调用
和传名调用
,前者的实现比较简单
1 | let x = 1 |
当使用传值调用
时,等价于fun(2)
,也就是先计算表达式x+1
的值,再作为函数参数传进去。
而传名调用
,将表达式直接作为函数传给函数,当使用时,再进行求值。
Thunk函数
就是使用传名调用
时,计算参数值的临时函数
1 | // 接上面的例子,等价于 |
会将传进来的参数放在一个临时函数里,需要使用参数时再调用临时函数。
实际上,在JS中,利用thunk函数来将多参变成单参函数
1 | var thunk = (eventName) => { |
通用的thunk转换函数
1 | var thunk = (fn) => { |
thunk函数的第二个括号里的参数是callback函数
yield
后面需要跟着thunk函数。
1 | function* g() { |
1 | function autoRun (fn) { |
Promise版本的自动执行
yield后面需要跟着一个可以返回Promise
的操作
转化为Promise的方法
1 | function toPromise(fn) { |
1 | function runOfPromise (fn) { |
Async/Await 大法
async/await 可以说是,真正的解决了异步的种种问题,而且代码可读性高,写法跟同步代码差不多。
async/await
可以看成generator函数
的语法糖,实际上就是generator的进一步封装。
await
的作用跟yield
类似。 跟generator函数不同的是,async函数会自动执行,不需要利用co
等工具。
async/await 进行HTTP请求的简单例子
1 | async function getUser(url) { |
在await外层写try catch
来捕获await后面的Promise
发生的错误
对于我个人来说,目前使用比较多的还是Promise和async/await,基本…怎么没使用的就是generator函数了
最后写个经常出现的例子
多个异步操作顺序执行。如,按顺序请求一组资源。
构造Promise调用链
1 | var urls = ['/aaa', '/bbb', '/ccc'] |
使用async/await
1 | var getByOrder = async (urls) => { |