【前端面经】JS-异步解决方案

同步和异步

众所周知, JavaScript是一门单线程的语言, 单线程就意味着同一时间只能执行一个任务, 当前任务执行结束, 才会执行下一个任务. 这种模式的好处就是执行环境比较单纯, 但坏处也很明显, 一旦有某个任务卡住了, 就会导致整个程序阻塞. 为了解决这个问题, JS将任务的执行模式分成了同步(Synchronous)异步(Asynchronous)两种.

更详细的同步异步的执行模式可以参考事件循环

下面我们就来讲讲JS异步编程的几种解决方案.

1.回调函数

回调函数是异步编程的最基本的方法, 它通常作为一个参数传递给另一个函数, 并且在父函数执行完成后执行回调函数

例如:

function logB(){
    
    
    console.log("b");
}

function parent(a, callback) {
    
    
    console.log(a);
    typeof callback === 'function' ? callback() : return
}

parent('a', logB)

在上面的例子中, 在parent函数中, 如果callback参数传入的是一个函数, 那么在输出形参a之后就会执行callback函数, 上述例子中logB就是作为回调函数传入parent中并执行

在实际开发过程中, 回调函数的使用场景还是很多的, 例如常见的数组遍历方法forEach, 其第一个参数就是回调函数

let arr = [1, 2, 3]
arr.forEach((item) => {
    
    
    console.log(item);
})

回调函数的优点是简单易懂, 但其实一旦嵌套起来很不利于代码的维护和可读性. 而且各个部分之间高度耦合, 流程会比较混乱. 最经典就是回调地狱(callback hell).
在实际开发过程中, 经常能遇到一些业务需要先发送一个请求获取数据后, 再根据这个请求的返回结果再发送第二个请求, 第三个请求可能又需要第二个请求的返回结果作为参数, 如此层层嵌套就称之为回调地狱, 这样的代码就很难维护.

ajax('url1', (res1) => {
    
    
    console.log(res1)
    ajax('url2+res1', (res2) => {
    
    
        console.log(res2)
        ajax('url3 + res2', (res) => {
    
    
            console.log(res3)
            ...
        })
    })
})

优点: 简单易懂
缺点: 多层回调函数嵌套时容易混乱, 不利于代码维护和可读性, 容易写出回调地狱

事件监听

这种方式, 异步任务的执行不取决于代码的执行顺序, 而取决于某个事件是否发生.

element.addEventListener('click', ()=> {
    
    
    console.log('click')
})

上面这个例子的含义是, 当element被点击时, 就会输出click

  • 优点: 比较容易理解, 并且可以绑定多个事件, 每个事件可以指定多个回调函数, 可以比较好的去耦合, 有利于实现模块化.
  • 缺点: 整个程序变成了事件驱动型, 运行流程很不清晰, 比较难看出主流程, 代码可读性低

Promise

Promise是一种处理异步代码(并且不会陷入回调地狱)的方式

Promise代表一个异步操作, 其有三种状态pending(进行中)fulfilled(已完成)rejected(已失败), 一个Promise对象必然处于这三种状态之一

  • pending: 初始状态, 既没有完成, 也没有被拒绝
  • fulfilled: 操作成功完成
  • rejected: 操作失败

Promise被调用后, 它会以pending开始, 代码会继续往下执行, 而Promise仍处于pending状态, 直到执行完成为止, 最终会以fulfilledrejected结束, 并对应的传给回调函数–then或者catch.
优点: 不仅可以解决回调地狱问题, 而且能够捕获错误并进行处理
缺点: 无法取消Promise

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。

语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
Generator 函数除了状态机,还是一个遍历器对象生成函数。
可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

async/await

本质上async和await是Promise的语法糖, 它可以使得异步代码看起来像同步代码一样

更加详细的Promise可以参考: Promise

总结

  1. JS 异步编程进化史: callbackpromisegeneratorasync/await
  2. async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
  3. async/await可以说是异步终极解决方案了。

(1) async/await函数相对于Promise, 优势体现在:

  • 处理 then 的调用链,能够更清晰准确的写出代码
  • 并且也能优雅地解决回调地狱问题。
    当然async/await函数也存在一些缺点:
  • 因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

(2) async/await函数对 Generator 函数的改进,体现在以下三点:

  1. 内置执行器。

    Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。

  2. 更广的适用性。

    co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

  3. 更好的语义。

    async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

猜你喜欢

转载自blog.csdn.net/achen0511/article/details/130443808
今日推荐