对async、await的理解,内部原理
此题主要考察地狱回调,Promise,async,await
首先需要了解什么是js代码执行机制
1 js代码执行机制
javascript是一门“单线程”语言。
JS为什么是单线程的?
最初设计JS是用来在浏览器验证表单操控DOM元素的是一门脚本语言,如果js是多线程的,那么两个线程同时对一个DOM元素进行了相互冲突的操作,那么浏览器的解析器是无法执行的。
JS为什么需要异步?
如果js中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。 对于用户而言,阻塞就以为着“卡死”,这样就导致了很差的用户体验。
JS异步的执行机制其实就是事件循环(eventloop)。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有等主线程任务执行完毕,”任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行。
异步任务有:定时器,延时器,ajax,事件处理,node js,文件读取也有异步。
参考链接
1.1 宏任务和微任务
宏任务:定时器,延时器,requestAnimationFrame,I/O
微任务:process.nextTick,Promise,Object.observe,MutationObserver
先执行同步任务,再取出第一个宏任务执行所有的相关微任务总会在下一个宏任务之前全部执行完毕,如果遇见异步任务就先微后宏。
1.进行第一轮事件循环的时候会把全部的js脚本当成一个宏任务来运行。
2.如果执行中遇到setTimeout之类的宏任务,那么就把这个setTimeout内部的函数推入宏任务的队列中,下一轮宏任务执行时调用。
3.如果执行中遇到promise.then()之类的微任务,就会推入到当前宏任务的微任务队列中, 在本轮宏任务的同步代码都执行完成后,依次执行所有的微任务。
4.第一轮事件循环中,当全部的同步脚本以及微任务队列中的事件执行完成后,这一轮事件循环就结束了, 开始第二轮事件循环。
5.第二轮事件循环同理先执行同步脚本,遇到其他宏任务代码块继续追加到宏任务的队列中,遇到微任务,就会推入到当前宏任务的微任务队列中,在本轮宏任务的同步代码执行都完成后, 依次执行当前所有的微任务。
6.开始第三轮循环往复..
一个小案例,可以思考如下代码输出顺序。
console.log(1)
setTimeout(function(){
console.log(3)
},0)
new Promise(function(resolve,reject){
console.log(4)
resolve()
}).then((response)=>{
console.log(7)
})
console.log(5)
new Promise会立刻执行,then事件会放在微任务里面
2 地狱回调如何形成的
假设有一个ajax请求,在第一个请求成功后,执行第二个请求……..
$.ajax({
url: "xxxxxx",
type: "post",
success: function(res){
consolo.log(res)
$.ajax({
url: "xxxxxx",
type: "post",
success: function(res){
consolo.log(res)
$.ajax({
url: "xxxxxx",
type: "post",
success: function(res){
consolo.log(res)
$.ajax({
url: "xxxxxx",
type: "post",
success: function(res){
consolo.log(res)
}
})
}
})
}
})
}
})
如此循环嵌套,就会产生地狱回调。解决地狱回调可以使用Promise,链式编程。
let p = new Promise(function(resolve,reject){
$.ajax({
url: "xxxxxx",
type: "post",
success: function(res){
resolve(res)
}
})
})
p.then((response)=>{
console.log(response)
return new Promise(function(resolve,reject){
$.ajax({
url: "xxxxxx",
type: "post",
success: function(res){
resolve(res)
}
})
}).then((response)=>{
console.log(reponse)
})
})
3 内部原理
async函数是什么?一句话,它就是Generator函数的语法糖。Generator函数可以返回一系列的值,因为可以有任意多个yield.
Generator函数是 ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
执行Generator函数会返回一个遍历器对象。
//Generator方式的函数里面的代码是分段执行看到yield 就分一段
function* helloGenerator( i
yield "hello' // yield类似暂停标记
yield "world'
return 'ending "
}
var hw = helloGenerator();
console.log(hw) //这个函数的结果是 ending吗? 不是,因为代码是暂停
console.log(hw.next()) //next表示拿出这个暂停的值
console.log(hw.next())
console.log(hw.next())
必须调用遍历器对象的next方法,使得指针移向下一个状态。
也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
换言之,Generator函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
假设有一个按钮,点击获取请求结果
let btn = document.getElementById("btn")
btn.onClick = async function(){
let res = await axios.get('xxxxxx') //等待成功,执行下面的语句
console.log("结果:"res)
}
async +await原理是 generate + yield的语法糖,async +await其实就是 generate + yield的写法,async +await可以省略then。
async +await使得异步代码看起来像同步代码。
4 async与await的缺点
await 需要与async配套使用,如果用在普通函数,会报错。
如果多个异步代码没有依赖关系,不要写成同步的形式,会阻塞程序执行(可以利用Promise.all方法进行包装),会造成性能上的降低。
- 本文作者: étoile
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!