js 之异步编程

前言

js 作为一门单线程的语言,则注定异步编程会是其最核心的内容。设想一下如果 js 不支持异步会怎样?

代码从上至下顺序执行,遇到计算量较大的算法则发生阻塞,即使后面有非常紧急的事情也只能等着。进而影响到用户的体验。

异步编程的发展过程

回调时期

在 es6 提供 promise 以前,处理异步操作必然绕不开回调,即执行完某方法后调用某个回调方法,伪代码如下

const _loadSomething = (callback) => {
	
    ....某异步请求,返回结果为 res
    
    callback(res);
}

_loadSomething((res) => {
	
    console.log(res);
});

但回调里如果还有异步操作或回调的回调里仍然有呢?


const callback = (res, cellCallback) => {
	cellCallback(res);
}

const _loadSomething = (callback) => {
	
    ....某异步请求,返回结果为 res
    
    callback(res, () => {
        callback(res, () => {
        	callback(res, () => {
            	console.log('end')
            })
        })
    });
}

_loadSomething(callback1);

如果遇到以上场景,那必然会出现回调层级过深,影响代码可读性

为了解决此问题,es6 推了 promise

Promise 时期

promise 的设计很巧妙,它将回调的形式转换为了链式调用

const promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('promise1')
  }, 1000)
}).then((value1) => {
  console.log(value1);
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('promise2')
    })
  })
}).then((value2) => {
  console.log(value2)
  return new Promise((resolve) => {
    resolve('promise3')
  })
})

上方代码在执行了 resolve 方法后会通知其观察者,也就是 then 中的回调函数,若 then 的回调中仍有异步操作,那可以继续创建 promise 对象,逐渐形成一个执行链。

promise 的出现无疑是一大进步、但可读性仍然没有同步代码强,那有没有办法将其看起来比较像同步执行呢

Generator 时期

使用 “*” 定义的 generator 方法内部的代码将被强制中断,只有调用了其方法所产生的 generator 对象的 next 方法才可执行到下一个 yield 处,next 方法会返回 yield 后的执行结果,若想第二个 yield 处需要使用,则可将值通过 next 的形参传入

function *lazyFunction() {
  console.log('start')

  const promise1 = yield new Promise((resolve) => {
    resolve(1)
  })

  yield new Promise((resolve) => {
    resolve(2)
  })
}

const generator = lazyFunction();
const { value: promise1 } = generator.next();
const { value: promise2 } = generator.next(promise1);

promise1.then((value) => {
  console.log(value)
})

promise2.then((value) => {
  console.log(value)
})

若在某个异步操作后才能执行某方法,使用 generator 后的代码将变成这样,在方法内部各个异步方法从上向下书写,一眼看去就像是串同步代码,但有一个问题是

generator 操作起来太麻烦了!

我需要定义懒函数,就是执行都需要手动 next,戳一下跳一下,只能说 generator 太不讲武德。

所以开发中最常用的 async 来了

Async Await

async await 实际上是一种对于 generator 的封装,将其手动执行变为自动执行

定义与 generator 基本一致,只是将关键字改为 async 和 await

async function asyncFunction() {
  const value1 = await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    })
  });

  console.log(value1)

  const value2 = await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(2)
    })
  });

  console.log(value2)

}

asyncFunction();

当代码执行到 await 处时,async 方法内部将暂停,只有当 promise 的 resolve 方法执行,代码才会继续向下走

注意:若 await 后的 promise 状态变为 fail ,即调用了 reject 方法,那 async 方法内部将彻底中断。

这样一来我们就再也看不见,过多的回调或繁琐的 next 操作了,对编程体验无疑有巨大的提升

不论是 promise、generator 还是 async await,其实都是代码设计上的优化,内部实际上还是回调加回调的形式,所以熟悉整个异步事件的执行过程显得更加重要

事件轮询

在 js 的异步编程中有一个事件轮询的概念,这里的事件指异步事件,轮询即是周期性的访问的意思。

js 代码在运行过程中遇到异步代码的解释过程如下

  1. 在经子线程计时后放进任务队列
  2. 主线程代码执行完毕,询问任务队列,有可执行任务则推入执行栈,执行完毕后弹出
  3. 进入下一次的轮询

值得注意的是,异步任务分为两种:宏任务、微任务(仅考虑在浏览器环境下)

宏任务微任务
setTimeoutpromise.then/catch/finaly
setInterval
requestAnimationFrame

所有代码执行的优先级关系为:微任务 --> 宏任务

也就是同在回调队列时,微任务执行优先级大于宏任务

所以下方代码的执行顺序是什么呢?

console.log(1)

setTimeout(() => {
  console.log(2)
}, 0)

setTimeout(() => {
  console.log(3)
})

new Promise((resolve) => {
  console.log(4)

  resolve('data')
}).then((data) => {
  console.log(data)
  console.log(5)
})

console.log(6)

自上而下解析

同步代码:1 --> 4 --> 6

微任务:data --> 5

宏任务:2 --> 3

所以最后的顺序是:1 --> 4 --> 6 --> data --> 5 --> 2 --> 3

所以可证实 js 代码的执行顺序为:同步代码 --> 微任务 --> 宏任务

写在最后

以上只是一些比较基础的知识,但以此衍生出的框架、库层出不穷,对于前端开发人员的挑战也越来越大,设计虽各有精妙,但原理却大同小异,所以我认为学习当以不变应万变,掌握最根本的技术,才能走的更远。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值