深圳幻海软件技术有限公司 欢迎您!

快点上车,前端异步编程发车了

2023-02-28

本文转载自微信公众号「零零后程序员小三」,作者003。转载本文请联系零零后程序员小三公众号。什么是异步编程?异步编程允许我们在执行一个长时间任务的时候,程序不用进行等待就可以继续执行后面的代码,直到任务完成后再以回调函数(callback)的方式回头通知你这种编程模式避免了程序的阻塞,提高了效率,它

本文转载自微信公众号「零零后程序员小三」,作者003 。转载本文请联系零零后程序员小三公众号。

什么是异步编程?

异步编程允许我们在执行一个长时间任务的时候,程序不用进行等待就可以继续执行后面的代码,直到任务完成后再以回调函数(callback)的方式回头通知你

这种编程模式避免了程序的阻塞,提高了效率,它适用于那些网络请求或者数据库操作的应用。

实现异步的方式

回调函数,是最简单的实现异步的方式

console.log('111'); 
 setTimeout(() => { 
     console.log("222"); 
        }, 2000) 
        console.log('333'); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

虽然,按照html文档的输出规则他是自上而下,但是在中间加了一个定时器,然后浏览器识别到了它,会马上执行,然后执行后面的代码,等到了给定时间,会以回调函数的方式返回。

在JS的设计之初,他一开始就是单线程的编程语言,尽管这里回调函数看上去和主线程一起进行的,但是都运行在一个线程中,况且主线程还运行其他代码。

虽然JS只有一个线程,但是还是有比较不错的优点的。因为所有的操作都在一个线程之中,所以不用去考虑资源竞争的问题,而且在源头就避开了线程之间的频繁切换,从而降低了线程开销

但是它也有一个致命的缺点,如果我们需要进行多个异步操作,我们可能会写出下面的代码

console.log("111"); 
 
       setTimeout(() => { 
           console.log("三秒后执行1"); 
           setTimeout(() => { 
               console.log("三秒后执行2"); 
               setTimeout(() => { 
                   console.log("三秒后执行3"); 
                   setTimeout(() => { 
                       console.log("三秒后执行4"); 
                   }, 3000) 
               }, 3000); 
           }, 3000); 
       }, 3000); 
 
       console.log("333"); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

如果再有别的回调,这样会更恐怖,一直写下去,换谁都看得心慌。我们管这个叫回调地狱

解决回调地狱

为了解决这个回调地狱,Promise诞生了。

我们在页面中动态的更新数据,也就是AJAX技术,就是使用Promise的API fetch()实现的

我们可以试一试用fetch()获取一个接口的数据

通过运行可知道他返回的是一个Promise对象,但是我们还没有获得我们想要的数据,因为Promise翻译一下就是承诺的意思,所以,他应该会在后来给我们实现我们想要的需求,所以,我在后面加个then,then翻译一下就也是然后的意思

所以就是传入它的then方法并传入一个回调函数,如果在后来这个请求成功之后,然后回调函数会被调起,请求的函数会被作为一个参数传入

fetch("http://jsonplaceholder.typicode.com/posts/1"
.then((response)=> ...) 
  • 1.
  • 2.

但是如果这样看来Promise和回调函数就没有区别了。

但是,Promise的优点在于它可以用一种链式结构将多个异步操作串联起来

也就是 比如下面的response.json()方法也会返回一个Promise,然后then之后就是将未来返回的response转换为json格式,

fetch("http://jsonplaceholder.typicode.com/posts/1"
.then((response)=>response.json()) 
  • 1.
  • 2.

然后我们还可以继续追加我们想要进行的操作,直接then下去,比如下面这样把结果打印出来或者把结果存到某个容器中等

fetch("http://jsonplaceholder.typicode.com/posts/1"
.then((response)=>response.json()) 
.then((json) => console.log(json)) 
  • 1.
  • 2.
  • 3.

Promise的链式调用避免了代码层层嵌套,尽管有很长的链式调用,但也只是将代码向下方增长而不是向右。可读性会大大提高。

但是在使用异步操作的时候也会遇到错误,比如各种网络问题以及数据格式不正确等。然后我们可以通过在末尾添加一个catch()来捕获这些错误,如果之前任意一个阶段发生了错误,那么catch会被触发,然后之后的then将不会再执行

这跟同步编程中用到的try/catch块相似,Promise还提供了finally方法,会在Promise链结束后调用,无论是否出现错误,我们都可以在这里做函数清理的工作。毕竟要有首有尾嘛。

async/await

现在来看一下async/await,简单来说就是基于Promise之上的语法糖,可以让异步操作更加简单明了。

具体步骤就是

首先使用async将返回值为Promise对象的函数标记为异步函数,就像是刚刚用到的fetch()就是一个异步函数,在异步函数中可以调用其他异步函数,不过不是使用then,而是用更简单的await,中文意思就是等到,等待,所以await会等待Promise完成之后直接返回最终结果

用了async/await之后就是将函数变成异步函数,可以直接获取到我们想要的结果,所以some已经是服务器返回的响应数据了,然后我们就可以进行响应的操作了

await虽然看上去会暂停函数的执行,但是在等待的过程中同样可以处理其他任务,比如我这里将返回的数据转换为json数据,因为await底层就是基于Promise和事件循环机制实现的,具体操作还有很多自行去尝试。

这样我们就拿到了我们想要的数据了。

但是使用的时候也要留意await的错误用法

比如我这样

async function fn() { 
 
            const some1 = await fetch("http://jsonplaceholder.typicode.com/posts/1"
            const some2 = await fetch("http://jsonplaceholder.typicode.com/posts/2"
            const some3 = await fetch("http://jsonplaceholder.typicode.com/posts/3"
            ... 
        } 
 
        fn() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

虽然看起来没有什么错误啊,但是这样写会打破这两个fetch()操作的并行,因为我们是等到第一个任务完成再执行第二个任务,然后再执行后面的代码。

所以我们有个小妙招。

就是将所有Promise用Promise.all组合起来,然后再去await,比如我下面的做法

async function fn() { 
 
            const some1 = await fetch("http://jsonplaceholder.typicode.com/posts/1"
            const some2 = await fetch("http://jsonplaceholder.typicode.com/posts/1"
            const some3 = await fetch("http://jsonplaceholder.typicode.com/posts/1"
             
            const [a,b,c] = await Promise.all([some1,some2,some3]) 
             
        } 
 
        fn() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

这样的做法会让运行程序效率提升很多。

最后,我们不能在全局或者普通函数中直接使用await关键字,await只在异步中有效,如果我们想要在最外层中使用await那么需要先定义一个异步函数,然后再在函数体中使用它

使用async await可以写更清晰更容易的理解异步代码,而且不用再使用底层的Promise对象,包括then(),catch()函数等

如果旧版本浏览器不支持async await语法,可以通过转译器编译成旧版本也兼容的代码