IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 异步编程:一次搞懂Promise,async,await -> 正文阅读

[JavaScript知识库]异步编程:一次搞懂Promise,async,await


前言

异步编程允许我们在执行一个长时间任务时,程序不需要进行等待,而是继续执行之后的代码,知道这些任务完成之后再回来通知你,通常是以回调函数(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("https://jsonplaceholder.typicode.com/posts")
        .then((response)=>{
            // ...
        });

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

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

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

fetch("https://jsonplaceholder.typicode.com/posts")
        .then((response) => response.json);

它代表在未来的某个时刻,将返回的数据转换成 JSON 格式,如果我们想等到它完成之后再执行其他的操作,我们可以在后面再追加一个 then 然后执行接下来的代码,比如将结果打印出来
结果打印图片
Promise 的链式调用避免了代码的层层嵌套,即便我们有一个很长的链,代码也不过是向下方增长而非向右,因此可读性会提升很多
在这里插入图片描述

三、错误处理

在使用异步操作的时候,我们也可能遇到错误,比如各种网络问题或者返回的数据格式不正确等等
在这里插入图片描述
如果我们要捕获这些错误,最简单的方法是附加一个catch在链式结构的末尾,如果之前任意一个阶段发生了错误,那么catch将会被触发,而之后的 then() 将不会执行,这和同步编程中用到的 try/catch 块很类似

fetch("https://jsonplaceholder.typicode.com/posts")
        .then((response) => response.json())
        .then((json) => {
            console.log(json);
        })
        .catch((error => {
            console.log(error);
        }));

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

fetch("https://jsonplaceholder.typicode.com/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 语法,我们还是可以使用转译器将它们编译成旧版本也兼容的等效代码。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:27:56  更:2022-03-22 20:28:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:48:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码