1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 异步编程:一次搞懂Promise async await

异步编程:一次搞懂Promise async await

时间:2019-09-30 19:41:49

相关推荐

异步编程:一次搞懂Promise async await

文章目录

前言一、回调函数二、Promise三、错误处理四、async/awaitawait使用时的陷阱123 总结

前言

异步编程允许我们在执行一个长时间任务时,程序不需要进行等待,而是继续执行之后的代码,知道这些任务完成之后再回来通知你,通常是以回调函数(callback)的形式,这种编程模式避免了程序的阻塞,大大提高了CPU的执行效率,尤其适用于IO密集的,例如需要经常进行网络操作数据库访问的应用。

如果大家还不是很清楚并发、并行、异步、同步的概念,可以先去看看我之前的博客

并发&并行&同步&异步的区别

一、回调函数

我们知道在javascript中有两种实现异步的方式,首先第一种是传统的回调函数,比如我们可以使用setTimeout() 让一个函数在指定的时间后执行

setTimeout(()=>{console.log("hello!");},3000);console.log("can you see me?");

这个函数本身会立刻返回,程序会紧接着执行之后的代码,而我们传入的回调函数则会等到预定的时间才会执行。

需要注意的是 JavaScript 从设计之初就是一个单线程的编程语言,即便看上去这里的回调函数和主函数在并发执行,但它们都运行在同一个主线程中。实际上主线程中还运行着我们写的其他代码,包括界面逻辑、网络请求、数据处理等。

虽然只有单个线程在执行,但这种单线程的异步编程方式其实有诸多优点。由于所有的操作都运行在同一个线程中,因此我们无需考虑线程同步或者资源竞争的问题,并且从源头上避免了线程之间的频繁切换,从而降低了线程自身的开销。

回调函数虽然简单好理解,但它有一个明显的缺点,如果我们需要依次执行多个异步操作,我们的程序可能会写成这样:

setTimeout(()=>{console.log('先等三秒');setTimeout(()=>{console.log('再等三秒');setTimeout(()=>{console.log('又等三秒');// ...},3000);},3000);},3000);

当第一个任务执行完毕以后,在回调函数里面再去执行第二个任务,然后是第三个、第四个…整个程序会一层接着一层的嵌套下去,可读性会变得非常差,这种情况也被叫做函数的“回调地狱”。为了解决这个问题,Promise 应运而生。

二、Promise

JavaScript 中使用 Promise 的 API ,fetch() 就是一个很好的例子,它用来发起一个请求来获取服务器数据,我们可以用它动态更新页面的内容,也就是我们平时说的 Ajax 技术,这里我调用 fetch() 去访问一个测试地址的数据:

fetch 会立刻返回返回一个 Promise 对象

这里的 Promise 几乎就是它的字面意思,它代表一个“承诺”,“承诺”这个请求会在未来某个时刻返回数据,我们随后可以调用它的 then 方法并传递一个回调函数

fetch("/posts").then((response)=>{// ...});

如果这个请求在未来成功完成,那么回调函数会被调起,请求的结果也会以参数的形式传递进来。

如果只是这样,Promise 和回调函数就没有什么区别了。其实 Promise 的优点在于 它可以用一种链式结构将多个异步操作串联起来

比如这里的 response.json() 方法也会返回一个 Promise

fetch("/posts").then((response) => response.json);

它代表在未来的某个时刻,将返回的数据转换成 JSON 格式,如果我们想等到它完成之后再执行其他的操作,我们可以在后面再追加一个 then 然后执行接下来的代码,比如将结果打印出来

Promise 的链式调用避免了代码的层层嵌套,即便我们有一个很长的链,代码也不过是向下方增长而非向右,因此可读性会提升很多

三、错误处理

在使用异步操作的时候,我们也可能遇到错误,比如各种网络问题或者返回的数据格式不正确等等

如果我们要捕获这些错误,最简单的方法是附加一个catch在链式结构的末尾,如果之前任意一个阶段发生了错误,那么catch将会被触发,而之后的 then() 将不会执行,这和同步编程中用到的 try/catch 块很类似

fetch("/posts").then((response) => response.json()).then((json) => {console.log(json);}).catch((error => {console.log(error);}));

类似的,Promise 还提供 finally 方法,他会在 Promise 链结束之后调用,无论失败与否,我们可以在这里做一些清理工作

fetch("/posts").then((response) => response.json()).then((json) => {console.log(json);}).catch((error => {console.log(error);}).finally(() =>{// 执行清理操作等等}));

四、async/await

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

首先我们需要使用 async 关键字将函数标记为异步函数(异步函数就是返回值为 Promise 对象的函数,比如之前用到的 fetch() 就是一个异步函数)。在异步函数中,我们可以调用其他的异步函数,不过我们不再需要使用 then() ,而是使用一个更加简洁的 await 语法,await 会等待 Promise 完成之后直接返回最终的结果

async function f(){const response = await fetch("https://...");}f();

所以这里的 response 已经是服务器返回的响应数据了。

需要注意的是,await 虽然看上起会暂停函数的执行,但在等待的过程中,JavaScript 同样可以处理其他的任务,比如更新界面、运行其他程序代码等等。这是因为await底层是基于 Promise 和时间循环机制实现的。

await使用时的陷阱

最后,我们在使用 await 的时候需要留意一下几个陷阱

1

比如在这个例子中,如果我们分别去 await 这两个异步操作,虽然不存在逻辑错误,但这样写会打破这两个 fetch() 操作的并行,因为我们会等到第一个任务执行完成之后才开始执行第二个任务

async function f(){const a = await fetch("https://.../post/1");const b = await fetch("https://.../post/2");// ...}

这里更高效的做法是将所有 Promise 用 Promise.all 组合起来,然后再去 await

async function f(){const promiseA = fetch("https://.../post/1");const promiseB = fetch("https://.../post/2");const [a,b] = await Promise.all([promiseA,promiseB]);// ...}

修改后的程序运行效率也会直接提升一倍。

2

如果我们需要在循环中执行异步操作,是不能够直接调用 forEach 或者 map 这一类方法的

尽管我们在回调函数中写了 await ,但这里的 forEach 会立刻返回,它并不会等到所有的异步操作都执行完毕。如果我们希望等待循环中的异步操作都一一完成之后才继续执行,那我们还是应该使用传统的 for 循环

async function f(){for (let i of [1,2,3]) {await someAsyncOperation();};console.log("done");}f();

更进一步,如果我们想要循环中的所有操作都并发执行,一种更炫酷的写法是使用 for await ,这里的for 循环依然会等到所有的异步操作都完成之后才继续向后执行

async function f(){const Promise = [someAsyncOperation(),someAsyncOperation(),someAsyncOperation(),];for await (let result of promises) {// }console.log("done");}f();

3

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

总结

使用 async/await 可以让我们写出更清晰、更容易理解的异步代码,有了它们之后,我们不再需要使用底层的 Promise 对象,包括调用它的 then(),catch() 函数等,即便是对于某些旧版本的浏览器,它们不支持 async 语法,我们还是可以使用转译器将它们编译成旧版本也兼容的等效代码。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。