js引擎线程是单线程的这个在事件循环介绍过,所以一些耗时的操作,如DOM操作、ajax操作、setTimeout等,都需要交给 其他web线程处理(js引擎是单线程,浏览器可不是单线程的),当有 返回的时候 将callback放入任务队列,待js执行栈调用
异步编程的语法目标,就是怎样让它更像同步编程
1.回调函数 callback
当异步调用有依赖关系的时候就需要用到上一次的返回结果,所以如何拿到上一次的返回结果,那 异步请求为例
ajax('xxx', (data1) => {
// 回调函数体
ajax('xxx', (data2) => {
// 回调函数体
// 这里用到了 data1 和 data2数据
})
})
这个例子就是最常用的回调使用,但是这种调用方式会有几个问题
- 嵌套层次太深,如果有10次请求,每次都依赖上一次返回结果,那就形成了回调地域,不利于代码的维护
- 很难处理异常,如果出现异常,如果每个回调里都做异常处理那写法会变得异常复杂
- 耦合性太强了,如果修改其中的一次请求可能就需要改动很多相关代码
2.Promise
因为callback的异步解决方式有上面种种问题,所以才出现了Promise的链式调用,每次then都会返回一个全新的Promise
ajax('1111')
.then(res => {
return ajax('2222')
})
.then( res => {
return ajax('3333')
})
....
这样就通过链式调用来解决了回调产生的问题,比callback 方法看上去舒服了很多,但promise 也存在问题
- Promise 一旦发生了就不能取消,会一直沿着then 方法执行,只能通过当前then方法的reject回调监听错误,或者用catch方法监听
- 写法还是不够优雅,需要一些列 的then
具体实现可以点击查看
文章待更新中.........
3.Generator
提到Generator, 就有必要提一下“协程”,这是传统语言中早有的异步编程方案,意思就是多个线程互相协作,完成异步任务,大体流程如下
- A开始执行
- A执行到一半,暂停,执行权转交给B
- 过段时间后B交还执行权到A
- A恢复执行
Generator是协程在ES6中的实现,最大特点就是可以交出函数执行权
function *gen() {
yield ajax('1111')
yield ajax('2222')
yield ajax('3333')
}
var g = gen()
var result1 = g.next()
var result2 = g.next()
var result3 = g.next()
-
Generator用法,它就是 js 函数的协程在ES6上的实现,可以暂停执行
上面代码就是Generator 函数封装的一个异步任务,异步任务需要暂停的地方都用yield语句注明,调用gen函数返回一个内部指针对象,调用 next 方法,会移动内部指针,指向遇到的yield语句,返回一个对象 包含 value(yield 表达式返回的值) 和 done(布尔值,便是当前Generator函数是否执行完毕) -
**交换数据和错误处理
**next 返回值 value 属性,是Generator 向外输出数据;next 方法还可以接受参数,是向Generator内部传值
还可以部署错误代码处理,捕获函数体外抛出的错误function * gen() { try{ var result = yield ajax('1111') }catch(e) { console.log(e) }} var g = gen() g.next() g.throw('error')
这样通过控制函数的执行实现了异步的控制,但是这种写法不够优雅,还得每次都调用next
co函数库做了一层封装,可以配合使用,比如
function * gen() {
var result1 = yield ajax('111') // ajax 返回一个promise
var result2 = yield ajax('222')
}
co(gen)
.then( res => {
// end
})
这样只要将gen传入co, 就会自动执行,并返回一个Promise对象
co执行原理
上面代码如果一步一步执行就会这样
var g = gen()
g.next().value.then( data => {
g.next(data).value.then( data => {
})
})
由于g.next() 的value 返回一个promise 对象,这样写的话如果有多个ajax 请求,就不够智能,还要一步一步 next ,所有封装一个自动执行器
function * gen() {
var result = yield ajax('111')
var result2 = yield ajax('2222')
}
function run(gen) {
var g = gen()
function next(data) {
var result = g.next(data)
if(result.done) {
return result.value // 判断gen 是够执行完毕
}
result.value.then( data => {
next(data)
})
}
next()
}
run(gen)
4.Async/await
async / await 应该是目前为止实现的异步代码同步化最好的实现方式,他是Generator函数的语法糖
async function fetch() {
var result1 = ajax('11111')
var result2 = ajax('22222')
var result3 = ajax('33333')
return [result1, result2, result3]
}
fetch()
.then( res => {
})
async是针对 Generator 函数改进
- 内置了自动执行器
- 语义更清晰