了解异步前,先得知道什么是单线程
单线程-只有一个线程,只能做一件事
代码示例
console.log(1)
alert('hello')
console.log(2)
点击确认后,才会打印出 2
JS 采用的是单线程,JS 修改 DOM 结构后,浏览器需要渲染,JS执行的时候,浏览器 DOM 渲染也会暂停,若是多线程的话,JS 同时修改 一个 DOM ,浏览器不知道如何渲染
但是单线程模式又无法满足一些需求,例如,获取后台数据,若是单线程模式的话,需要等到数据返回,才能做其他事情,此时若是网络不好,接下来的操作也无法执行了
所以出现了异步解决方案。
异步
代码示例
setTimeout(function() {
console.log(1)
}, 1000)
setTimeout(function() {
console.log(2)
})
console.log(3)
异步在 JS 中是如何实现的呢?通过 event-loop。
event-loop
事件轮询,状态变化的过程,参考nodejs文档,JS 实现异步的原理,同步代码直接执行,异步代码先放在异步队列中,待同步代码执行完毕后,轮询执行异步队列中的代码。
以上代码的执行过程
- function() { console.log(2) } 立即放入异步队列中,1s 后 function() { console.log(1) } 被放入
- 先执行主进程中的 console.log(3) ,执行完后,把先放入的拿到主进程中
- 在主进程中依次执行 function() { console.log(2) },function() { console.log(1) }
- 最后输出 '3 2 1'
只要主线程空了,就会去读取“异步队列”,这个过程是不断重复的,称之为 event-loop。
继续看上面的代码,发现一些问题
- 没按照书写方式,从上往下执行,可读性差
- 回调函数不容易模块化,容易出现回调地狱
着重说一下回调地狱
看一个例子
function fn1() {
setTimeout(()=>{
console.log('fn1')
}, 1000)
}
function fn2() {
setTimeout(()=>{
console.log('fn2')
}, 1000)
}
function fn3() {
setTimeout(()=>{
console.log('fn3')
}, 1000)
}
对于以上代码如何实现: 1s之后输出 fn1, 再过1s输出 fn2, 再过1s输出 fn3 ?
做如下改造
function fn1(callback) {
setTimeout(()=>{
console.log('fn1')
callback()
}, 1000)
}
function fn2(callback) {
setTimeout(()=>{
console.log('fn2')
callback()
}, 1000)
}
function fn3() {
setTimeout(()=>{
console.log('fn3')
}, 1000)
}
fn1(function(){
fn2(function(){
fn3()
})
}) //先执行 fn1() 再执行 fn2(),最后执行 fn3()
调用时,回调函数层层嵌套,易读性非常差,这就是回调地狱。
如何解决这些问题呢?Promise 可以解决
Promise
promise是对异步调用的封装,是一种异步的解决方案。
基本语法
function fn() {
return new Promise((resolve,reject)=> {
resolve() //成功时调用
reject() //失败时调用
})
}
fn().then(success, fail).then(success2, fail2)
promise 是一个对象,对象里存贮一个状态,这个是可以随着内部的执行转化的,为以下三种状态之一:等待态(Pending), 完成态(Fulfilled), 决绝态(Rejected)
一开始,先设置好 等状态从pending 变成 fullfilled 和 rejected 的预案(也就是当成功后做什么,失败时做什么)
promise 启动后,当满足成功条件时让状态从 pending 变成 fullfilled (执行 resolve)当满足失败的条件时,我们让状态从 pending 变成 rejected (执行 reject)
解决上述问题
function fn1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('fn1...')
resolve() //成功时的回调
}, 1000)
})
}
function fn2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('fn2...')
resolve() //成功时的回调
}, 1000)
})
}
function fn3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('fn3...')
resolve()
}, 1000)
})
}
function onerror() {
console.log('error')
}
fn1()
.then(fn2)
.then(fn3)
.catch(onerror) //依次输出:fn1... fn2... fn3...
上述代码把 callback 的嵌套执行改为 then 的串联执行,可读性好了些。
Promise.all
接收一个 promise 对象的数组,待全部成功后,就会调用 success
Promise.all([promise1, promise2]).then(success, fail)
Promise.race
接收一个 promise 对象的数组,promise1 和 promise2 只要有一个成功后,就会调用 success
Promise.race([promise1, promise2]).then(success, fail)
then 只是将 callback 拆分了,还是存在回调函数,当 then 多了后,可读性依然有些差,async await 可以解决这问题。
async await
async 是对 promise 的扩展,同步的写法去使用,再也没有回调函数
var w = waitHandle()
w.then(success1,fail1)
.then(success2,fail2)
.then(success3,fail3)
改为
const success = async function(){
const result = await waitHandle()
console.log(result)
}
success()
同步的写法,可读性更好了。
捕获异常
多个await
命令,可以统一放在try...catch
结构中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
转载于:
https://zhuanlan.zhihu.com/p/78816710
多谢分享