js异步 从callback 到 async

js异步

因为js是单线程的语言,所以为了不使页面卡顿,暂停。js引入了异步机制。

问:那么js为什么是单线程的呢?

答:因为js是可以操作dom元素的,所以js就必须单线程的。否则页面渲染就会出问题。

es6以前的处理方式

众所周知,es6以前的是通过回掉函数callback来处理异步的逻辑的。这种处理方式正常情况下 没有什么问题,但是如果碰到多个函数层层嵌套 就会出现回掉地狱。形成一个类似于金字塔的样子,虽然执行上并没有问题,但是非常不利于维护和阅读。想想如果 产品经理忽然说需求变了 需要你在b里面在加一段逻辑,再调用一下e函数,然后按照a,b,e,c,d的顺序执行。(这个时候 如果不小心删掉了一个大括号,那么开发人员的心态就会崩溃,怀疑自我。想转行!)(以下代码瞎写的,表达个意思而已,懂吧)

function  a(){
    function  b(){
        function c(){
            function D(){
                console.log('a,b,c,d 都按顺序执行了')
            }
        }
    }
}

es6的promise

所以es6的promise应运而生。promise的产生是为了让我们脱离回掉地狱。过上链式调用的好日子。这样代码阅读起来会舒服很多很多。如上面那段逻辑 我们就可以改写为

function a() {
 return new Promise((resolve,reject)=>{
     return 'a'
 })
}
function b() {
    return new Promise((resolve,reject)=>{
        return 'b'
    })
}
function c() {
    return new Promise((resolve,reject)=>{
        return 'c'
    })
}
function d() {
    return new Promise((resolve,reject)=>{
        return 'd'
    })
}
a().then(()=>{
    return b();
}).then(()=>{
    return c();
}).then(()=>{
    return d()
}).then(()=>{
    console.log('a,b,c,d 都按顺序执行了')
})

这样的话如果 你要在b 后面加一个e函数 你就可以直接在中间插一段就行了。代码会非常的整齐,逻辑看上去都清晰很多。

promise 有三种状态,pending, resolved,rejected. 刚定义出来的promise 就是pending状态。 Pending 可以转为resolved 也可以转为rejected 过程不可逆。resolved的状态被then接收,rejected状态可以被catch接收。(这里说的resolved也就是fulfilled的意思)

es2017的async

虽然 promise把异步变成了链式调用,但是async 能让我们用同步的语法去写异步的逻辑。这个就很强大,让你写代码的时候 不需要考虑什么同步异步,达到润物无声的高级感。

我们都知道async 是promise的语法糖,那么怎么理解语法糖这个意思呢,就是说是 他本质上还是promise 的逻辑,但是形式上不一样而已。那怎么证明async不是新的类型呢。可以定义一个async函数,然后直接打印这个函数 就会发现他执行后返回值还是一个promise对象。如下

async function a(){}  a();
/*Promise {<fulfilled>: undefined}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined*/

async 既然是promise的语法糖,那promise有的功能 它也都有。promises使用时最常用的then函数,在async里面对标的就是await函数。所以 await不能单独调用哦,必须放在async里面。使用方法如下

async function a(){return 'helloWorld'}
async function b() {
    console.log("*****")
    let hello=await a();
    console.log(hello);
    console.log("888")
}
b();
//*****
//helloWorld
//888

那promise 对应的catch函数,在这里需要用其它语言的写法,try catch来处理。

也就是说 要用try来包裹await函数,在catch里写上异常处理逻辑。

这里有个较为常见的面试题。

问:我们知道await函数执行过程中,如果中断了,是不会继续往下走的。那如果有的报错,我们其实是希望他提示一下,但是不要中断我的操作,那我们要怎么做呢?

答:将要处理的这段异步代码用try catch包裹起来,在catch里面加上错误提示,再将try里面包裹的 await后面的代码 再写一份到catch中,让其运行。

或者,我们可以用async和promise相结合的方式,用promise的catch里面写出错后的逻辑。

具体代码可以大概概括如下(表达个意思,不是复制出去就能执行的)

async function a(){return 'helloWorld'}
async function b() {
    console.log("我是执行异步前的正常逻辑");//1
    try {
        let hello=await a();
        console.log("我是执行异步后的其它代码")//2
    }catch (e) {
        console.error(e)//错误提示
        console.log("我是执行异步后的其它代码")//2语句复制过来的
    }

}
//或与promise结合
async function b() {
    console.log("我是执行异步前的正常逻辑");//1
    a().then(()=>{
        console.log("我是执行异步后的其它代码")//2 
    }).catch((e)=>{
        console.error(e)//错误提示
        console.log("我是执行异步后的其它代码")//2语句复制过来的
    })
}

异步的处理机制-事件循环

说到异步 那不能提到异步的处理机制-事件循环(event-loop)

具体流程 我用下面的简图表示出来了(可能图不太标准 ,主要看文字)

 

 

就是说 一段代码下来,

1.我们首先判断当前语句是否是同步的正常语句,是的话,我们就推入运行栈中 运行,运行完,将这个语句推出栈。如果碰到异步的语法,我们先把他们放到消息队列里等待着,继续往下走。

2.如果同步代码已经全部执行完毕,那么运行栈暂时清空,此时,事件循环就好像一个哨兵一样,发现了这个消息,他去消息队列里找我们刚刚暂存在里异步函数,然后继续重复第一步,判断这个异步函数中的语句 是否是同步的。。

3.事件循环这个哨兵就像永动机一样,不需要休息,一直存在,一直循环监听。就实现了我们的异步逻辑实现。

宏任务与微任务

宏任务指的是浏览器指定的一些异步函数,比如点击事件,定时器事件,ajax事件

微任务指的是js指定的异步函数,比如promise和async await。

问:为什么要区分宏任务和微任务

答:因为宏任务和微任务 在事件循环中执行的时机是不一样的。(可能会遇到一些面试题里会问这个)

问:宏任务和微任务哪个先执行

答:微任务先执行,宏任务后执行。微任务在dom渲染前执行,宏任务在dom渲染后后执行。(这个就不要问为什么了,人家浏览器就这么定了)

posted @ 2020-10-10 17:10  Ada_Blog  阅读(751)  评论(0编辑  收藏  举报