优秀的编程知识分享平台

网站首页 > 技术文章 正文

突破传统JavaScript异步模式:async/await 的应用技巧

nanyue 2024-08-04 16:53:11 技术文章 8 ℃

如果大家需要八股文,或者想要跳槽、涨薪、进阶学习,那么可以留意公重号:码农补给站同时也会更新前端的时事

前言

JavaScript 是一种单线程的编程语言,这意味着它在同一时间内只能执行一个任务。但是我们可以通过使用异步编程模型,例如使用回调函数、Promise、async/await 等机制,可以实现非阻塞(Non-blocking)的并发操作,提高程序的响应性能。

异步编程和同步编程是什么?

JavaScript是一门广泛应用于Web开发的编程语言,它支持异步和同步编程。

同步编程

同步编程是指代码按照顺序执行,每条语句都必须等待前一条语句执行完成后才能继续执行下一条语句。这种编程方式是最基本的编程方式,也是最容易理解的编程方式。

例如,以下代码段演示了同步编程:

console.log("开始执行");
let sum = 0;
for(let i =1;i<=100;i++){
sum += i;
}
console.log(`1到100的和为${sum}`);
console.log("执行结束");

在上述代码段中,当程序运行时,首先输出“开始执行”,然后计算1到100的总和并输出结果,最后输出“执行结束”。由于代码按顺序执行,因此当执行完计算总和的代码段后,才会执行最后一行代码。

异步编程

异步编程是指当代码执行到某个需要耗费时间的操作时,不会一直等待该操作完成,而是立即执行下一行代码。当操作完成后,它会通知代码,代码再继续执行。这种编程方式可以提高系统的吞吐量和响应速度。

例如,以下代码段演示了异步编程:

console.log("开始执行");
setTimeout(()=>{
console.log("3秒后输出")
},3000);
console.log("执行结束");

在上述代码段中,当程序运行时,首先输出 开始执行 ,然后执行一个计时器,等待三秒钟后输出 3秒后输出,最后输出 执行结束。由于计时器是异步操作,因此代码不必等待三秒钟,而是立即执行下一行代码。当计时器完成时,它会通知JavaScript引擎,引擎再继续执行计时器回调函数内的代码。

再看下面代码:

function a() {
setTimeout(() => {
console.log('A');
}, 1000)
}
function b() {
console.log('B');
}
a()
b()

在上面代码中,a() 函数是一个异步函数,它使用了 setTimeout 来模拟一个耗时操作。而 b() 函数是一个同步函数。

根据 JavaScript 的事件循环机制,异步操作会先于后续的同步操作执行。因此,在调用 a() 后,会先打印出 B,然后在约 1 秒后打印出 A。

所以,代码的输出结果将是:

B
A

同步和异步编程是JavaScript中最基本的两种编程方式。同步编程会按照顺序执行,每个语句都必须等待前一条语句执行完成后才能继续执行下一条语句。而异步编程则是当代码执行到某个需要耗费时间的操作时,不会一直等待该操作完成,而是立即执行下一行代码。当操作完成后,它会通知代码,代码再继续执行。异步编程可以提高系统的吞吐量和响应速度。

回调函数(Callback Function)

回调函数是一种常见的编程概念,它是指将一个函数作为参数传递给另一个函数,并在特定条件或事件发生时被调用执行的函数。

通过使用回调函数,可以避免阻塞线程并允许程序继续执行其他任务。

下面我们通过一个简单的例子进行说明:

function a(c) {
setTimeout(() => {
console.log('A');
}, 1000)
}
function b() {
console.log('B');
}
a()
b()

在上面我们已经明白这段代码会先输出B,再输出A。那我们有什么办法可以让这段代码先输出A,再输出B呢。

这时候我们可以用上回调函数:

function a(callback) {
setTimeout(() => {
console.log('A');
callback();
}, 1000)
}
function b() {
console.log('B');
}
a(b)

当你调用函数 a 时,并传入函数 b 作为回调函数的参数:a(b);

函数 a 内部使用 setTimeout 函数设置一个定时器,1 秒后异步执行回调函数,并打印出字母 A:

setTimeout(() => {
console.log('A');
callback(); // 在异步操作完成后调用传入的回调函数
}, 1000);

在这里,callback 指向传入的回调函数 b。

因此,当定时器结束时,会先打印出 'A',然后调用传入的回调函数 b: 最终的控制台输出结果为:

A
B

这种方式可以实现异步操作的顺序控制,即在异步操作完成后再执行相关的操作,避免了异步操作和后续逻辑的竞争问题。

接下来我们定义xq和 marry 为函数:

function xq(callback) {
setTimeout(() => {
console.log('涛哥相亲了');
callback('相亲成功');
}, 2000);
}

function marry(callback) {
setTimeout(() => {
console.log('涛哥结婚了');
callback();
}, 1000);
}

function baby() {
console.log('小小涛');
}

xq((res) => {
console.log(res);
marry(() => {
baby();
});
});

这段代码定义了三个函数 xq、marry 和 baby,然后在主程序中调用了这些函数以展示它们的交互。

  1. xq 函数:
  2. 这个函数模拟"涛哥相亲"的过程。
  3. 它接受一个回调函数作为参数,并使用 setTimeout 来模拟一个2秒的延迟。
  4. 在2秒后,会输出"涛哥相亲了",然后调用传入的回调函数,将字符串"相亲成功"作为参数传递给回调函数。
  5. marry 函数:
  6. 这个函数模拟"涛哥结婚"的过程。
  7. 同样地,它也接受一个回调函数作为参数,并使用 setTimeout 来模拟1秒的延迟。
  8. 在1秒后,会输出"涛哥结婚了",然后调用传入的回调函数。
  9. baby 函数:
  10. 这个函数用来输出"小小涛"。

接下来,在主程序中:

  • 调用 xq 函数,并传入一个回调函数。当相亲成功后,这个回调函数会打印出"相亲成功",然后调用 marry 函数。
  • 在 marry 函数的回调中,调用 baby 函数,打印出"小小涛"。

根据代码,程序的输出如下:

复制代码涛哥相亲了
相亲成功
涛哥结婚了
小小涛

尽管回调函数在某些情况下非常有用,但它们也存在一些不足之处:在需要进行多个异步操作的复杂场景中,使用大量嵌套的回调函数会导致代码难以理解和维护。这种情况下,回调函数的嵌套层级会逐渐增加,造成代码可读性差、难以调试和错误处理复杂等问题。

Promise

Promise是JavaScript中一种处理异步操作的机制。它可以看作是对异步操作的包装,用于更方便地处理异步任务的完成或失败,并获取最终的结果。

Promise((resolve, reject) => {} 表示创建了一个 Promise 对象。

在这个 Promise 中,resolve 和 reject 是两个函数参数,用于在异步操作完成时进行处理。resolve 用于将 Promise 标记为成功,并返回成功的结果;reject 用于将 Promise 标记为失败,并返回失败的原因。

.then() 方法返回一个新的 Promise 对象,这个 Promise 对象的状态和值由其参数回调函数的返回值决定。如果回调函数返回一个值,那么返回的 Promise 对象将会处于已完成状态,并且其值将会是这个回调函数的返回值;如果回调函数抛出异常,那么返回的 Promise 对象将会处于已拒绝状态,并且其原因将会是这个异常。

还是根据前面回调函数的代码我们用promise处理异步任务:

function xq() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('涛哥相亲了');
resolve('相亲成功')
}, 2000)
})
}
function marry() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('涛哥结婚了');
resolve()
}, 1000)
})
}
function baby() {
console.log('小小涛');
}
xq()
.then((res) => { // then自己就会返回一个promise对象
console.log(res);
return marry()
})
.then(() => {
baby()
})

在主程序部分,我们首先调用了 xq() 函数,它返回的是一个 Promise 对象。然后我们使用 then 方法注册了一个成功的回调函数,该回调函数接收 xq() 函数成功后返回的结果 res,并在该回调函数中打印了 res,然后返回了 marry() 函数的 Promise 对象。

接着,我们再次使用 then 方法注册了另一个成功的回调函数,用于处理 marry() 函数成功后的操作。在这个回调函数中,我们直接调用了 baby() 函数。

需要注意的是:由于 then 方法会返回一个新的 Promise 对象,因此可以通过链式调用多个 then 方法,使得多个异步操作串行执行。在这段代码中,我们就使用了链式调用的方式,让 xq()、marry() 和 baby() 这三个异步操作按照顺序执行。

根据代码,程序的输出如下:

复制代码涛哥相亲了
相亲成功
涛哥结婚了
小小涛

补充

Promise的作用有以下几点:

  1. 异步操作的管理:Promise提供了一种结构化的方式来管理异步操作,使得代码更易读、维护和扩展。通过使用Promise,可以避免回调地狱(callback hell)的问题。
  2. 提供清晰的状态和结果:Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。通过Promise对象,可以清晰地知道异步操作的状态和最终结果。
  3. 链式调用和错误处理:Promise支持链式调用,可以在一个Promise的成功回调中返回另一个Promise,实现多个异步操作的顺序执行。同时,Promise还提供了.catch()方法,用于捕获和处理可能出现的错误。
  4. 更好的错误处理:使用Promise,可以通过.catch()方法捕获到异步操作中的错误,并进行适当的处理。这样可以避免错误被忽略或无法捕获的情况。

总的来说,Promise提供了一种优雅的方式来处理异步操作,使得代码更可读、可维护,并提供了更好的错误处理机制,提高了代码的可靠性和稳定性。

async/await

async/await 是 ES2017 中引入的一种处理异步操作的语法,它可以让异步代码的写法更加清晰和简洁。在使用 async/await 时,我们可以使用 async 关键字来定义一个异步函数,同时使用 await 关键字来等待一个 Promise 对象的状态。

还是以上面promise的代码改用async/await来处理:

function xq() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('涛哥相亲了');
resolve('相亲成功'); // 相亲成功,调用resolve方法并传递相亲成功的信息
}, 2000);
});
}
function marry() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('涛哥结婚了');
resolve();
}, 1000);
});
}
function baby() {
console.log('小小涛');
}
async function foo() {
await xq(); // 等待Promise对象完成,并将结果保存到变量res中
console.log(res); // 输出相亲成功的信息
await marry();
baby();
}
foo();

相当于:async === return new Promise((resolve, reject) => {})

async/await和Promise是JavaScript中处理异步操作的两种不同方式,它们有一些相似之处,但也存在许多区别。

  1. 语法差异

Promise是一种基于回调函数的异步编程模式,其语法相对比较繁琐,需要使用.then()或.catch()方法来处理异步操作的结果或错误。而async/await则是一种更为简洁、直观的异步编程方式,它依赖于ES7引入的async和await关键字,可以将异步操作的处理流程写成类似同步代码的形式,使得代码更易于理解和维护。

  1. 错误处理方式

在Promise中,通过.catch()方法来捕获异步操作产生的错误,并进行相应的处理。而在async/await中,可以使用try/catch语句来捕获异步操作产生的错误,这种方式更加直观和简洁。

  1. 返回值处理方式

在Promise中,可以使用.then()方法或.catch()方法来获取异步操作返回的值或错误信息。而在async/await中,通过await关键字等待异步操作返回的值,并将其保存到变量中,可以更加方便地对异步操作的返回值进行处理。

  1. 处理多个异步操作的方式

在Promise中,可以使用Promise.all()方法来处理多个异步操作的执行并行和结果收集。而在async/await中,可以通过将多个异步操作的await语句放在同一个async函数中来处理多个异步操作的执行和结果收集。

总之,async/await是一种更为简洁、直观的异步编程方式,相比Promise更加易于理解和维护。但是,在某些场景下,Promise也具备一些优势,比如对于多个异步操作的执行并行和结果收集等方面。因此,在实际开发中,可以根据具体需求选择合适的异步编程方式。


原文链接:https://juejin.cn/post/7312352501406892068

Tags:

最近发表
标签列表