📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
- 写在前面的话
- JavaScript 异步编程
- 技术简介
- 回调地狱
- Promise
- async、await
- 其他用法
- 总结陈词
写在前面的话
异步编程允许我们在执行一个长时间任务时,程序不需要等待,而是继续执行之后的代码,直到任务完成后再通知你,通常是以回调函数的形式。
这种编程模式,避免了程序的阻塞,提高了CPU的执行效率,用户体验得到了提升。
以 Java 中异步编程为例,其用法丰富多彩,可以利用JUC
等各种工具方法实现多线程效果,以此提升系统系统,比如下面示例代码。
自定义一个 Callable 接口,利用线程池提交,返回 Future 对象,通过调用 get 方法阻塞等待结果。
Future<?> future = executorService .submit(new MyCallable());
System.out.println("结果为:" + future.get());
回到正题,接下来介绍一下 JavaScript 中异步编程的运用,让我们开始。
JavaScript 异步编程
技术简介
JavaScript
从设计之初就是一个单线程的编程语言,浏览器无论在什么时候都有且只有一个线程在运行JavaScript
程序,即同一时间只执行一条代码,所以每一个JavaScript
代码执行块会“阻塞”其它异步事件的执行。
但依然不能阻止JavaScript
实现异步编程效果,来一段示例代码:
setTimeout(() => {
console.log('Hello World')
}, 1000)
console.log('123')
很明显,先输出123,再输出Hello World,这里可以看一下之前的这篇博文:《setTimeout 简笔》
JavaScript
的单线程的异步编程方式其实有诸多优点,由于所有操作都运行在同一个线程中,无须考虑线程同步和资源竞争的问题,从源头上避免了线程之间的频繁切换,降低了线程自身的开销。
回调地狱
JS 中常见的异步场景,除了 setTimeout,还有 AJAX、Axios、Fetch 等远程数据获取,都是利用回调函数实现异步效果。
回调函数虽然方便,但它有一个明显的缺点。
如果我们需要依次执行多个异步操作,代码可能变成下面这样,典型的“向右编程”,可读性和可控性都很差。
setTimeout(() => {
console.log('Hello World')
setTimeout(() => {
console.log('Hello World')
setTimeout(() => {
console.log('Hello World')
},1000)
},1000)
},1000)
这种情况也被叫做函数的“回调地狱”。
为了解决这个问题,Promise
应运而生。
Promise
Promise 是ES6引进的关键,它一个对象,代表了一个异步操作的最终完成(或失败)及其结果。它允许你为异步操作的结果提供一个回调函数,而不是使用传统的回调函数嵌套。
Promise 的创建:
使用 new Promise() 构造函数,并提供一个执行器函数(executor),这个函数接受两个参数:resolve 和 reject。resolve 用于解决(fulfill)Promise,而 reject 用于拒绝(reject)Promise。
Promise 的状态:
pending(等待态)、fulfilled(完成态)、rejected(拒绝态)。
Promise 的方法:
.then() 用于处理 Promise 成功的结果,.catch() 用于处理 Promise 失败的结果,.finally() 用于在 Promise 成功或失败后执行一些操作。
Promise 的示例:
// 使用new Promise方式改写,意义其实不大,这个本身其实就是
// 这段代码手动创建了一个Promise,并在其构造函数中提供了执行函数,这个函数接收两个参数:resolve和reject。这两个函数分别用于在异步操作成功或失败时调用。
// 当this.$axios.get('/api/hello')成功返回响应时,我们调用resolve并传递响应数据。如果发生错误,我们调用reject并传递错误对象。
// 然后,你可以链式调用.then()来处理成功的结果,以及.catch()来处理任何可能出现的错误。这种方式允许你以更传统的方式处理异步操作
// 但它通常没有使用async/await那么简洁和直观。
let sayHello = new Promise((resolve, reject) => {
this.$axios.get('/api/hello')
.then(response => {
resolve(response);
})
.catch(error => {
reject(error);
});
})
sayHello.then(response => {
console.log('使用new Promise方式改写,成功:' + response.data)
}).catch(error => {
console.log('使用new Promise方式改写,失败:' + error)
});
Tips:上面都是一些理论,接下来用企业开发常用的 Fetch 技术加以说明。
常用的fetch
和Axios
都是基于Promise的,这意味着它们都使用 Promise 来处理异步操作。
浏览器直接输入fetch函数,可以看到返回值是一个 Promise 对象,Promise 字面意思是承诺,承诺这个请求会在未来某个时刻返回数据。
那是怎么解决回调地域的?可以看下面示例代码,Promise 的链式调用避免了代码的层层嵌套,很明显不再是“向右编程”,而是“向下编程”。
fetch('http://127.0.0.1:8082/api/hello')
.then(response => {return response.json()})
.then(data => { console.log("get.data:", data)})
.catch(error => {console.log("get.error:", error.message)})
.finally(() => {console.log("get.finally")})
//输出:get.data: Hello, Spring Boot 2!
//输出:get.finally
如果在链式调用的时候,遇到错误,可以用catch,如果之前任意一个阶段报错,都会被catch捕捉,后续流程则不执行。
同时可以添加finally做兜底动作,无论失败与否都会执行,这里面可以执行 Loading 动画关闭等事情。
其实和 Java 的try-catch-finally
代码块差不多,链式调用就有点像 Java8 的 Stream。
好吧,再次证明语言是互通的,条条大路通罗马。
async、await
async 和 await 是 ES2017(ES8)中引入的,它们基于 Promise 提供了更简洁的语法来编写异步代码。
简单来说,它们是基于 Promise 之上的一个语法糖,可以让异步操作更加简单明了。
【基础使用】
如下代码,函数添加了 async 代表是异步函数,内部利用 await 可以阻塞该操作,等待结果的返回。
这里的 this.$axios.get
返回的是一个 Promise 对象,await 通常搭配其使用,当然也可以跟同步代码,都会阻塞。
注意,此时如果函数外有其他逻辑,不会被阻塞。
async fetchData() {
try {
const response = await this.$axios.get('/api/sleep');
this.data = response.data;
} catch (error) {
this.error = error.message;
}
},
【补充说明】
并不是 async 函数就必须和 await 一起使用。你可以声明一个 async 函数,但完全不使用 await 关键字。在这种情况下,函数体会同步执行,并且函数会直接返回一个状态为 fulfilled 的 Promise,其值为函数的返回值。
但是,await 关键字只能在 async 函数内部使用。这意味着你不能在非 async 函数中使用 await。
【再一个示例】
// 定义一个返回 Promise 的异步函数
function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve("data");
}, 1000);
});
}
// 使用 async 和 await 的函数
async function fetchDataAsync() {
const data = await fetchData(); // 等待 fetchData() 解决并获取结果
console.log(data); // 'data'
}
// 调用异步函数
fetchDataAsync();
在这段代码中,fetchData 返回一个 Promise,而 fetchDataAsync 是一个异步函数,它等待 fetchData 的 Promise 解决,然后打印结果。
使用 async 和 await 可以使异步代码看起来更像是同步代码,从而简化了异步编程的复杂性。
其他用法
【Promise.all】
如果 async 函数中,有多个异步请求逻辑,使用多个 await 关键词会导致效率较低。
此时可以通过 Promise.all 的方式改造,具体参考下面代码,效率提升接近一倍。
async fetchData() {
// 创建两个Promise对象
let handleOne = this.$axios.get('/api/hello');
let handleTwo = this.$axios.get('/api/sleep');
// Promise.all方法改写
// 等数组中的所有promise对象都完成执行
const [a, b] = await Promise.all([handleOne, handleTwo]);
console.log(a)
console.log(b)
}
还有一个更酷炫的写法,for await,也会等所有操作都执行完才向后执行。
async fetchData() {
let handleOne = this.$axios.get('/api/hello');
let handleTwo = this.$axios.get('/api/sleep');
let asyncArr = [handleOne, handleTwo]
for await (let result of asyncArr) {
console.log(result)
}
console.log('done')
}
【Promise几种常用方法】
1、Promise.all()
将多个Promise封装成一个新的Promise,成功时返回的是一个结果数组,失败时,返回的是最先rejected状态的值。
使用场景:一次发送多个请求并根据请求顺序获取和使用数据。
2、Promise.race()
返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
简单来说,就是多个Promise中,哪个状态先变为成功或者失败,就返回哪个Promise的值。
3、Promise.any()
接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
4、Promise.allSettled()
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。
【关于 async 和 await 的使用】
并不是 async 函数就必须和 await 一起使用。你可以声明一个 async 函数,但完全不使用 await 关键字。在这种情况下,函数体会同步执行,并且函数会直接返回一个状态为 fulfilled 的 Promise,其值为函数的返回值。
但是,await 关键字只能在 async 函数内部使用。这意味着你不能在非 async 函数中使用 await。
// 不使用 await 的 async 函数
async function synchronousFunction() {
// 这里没有使用 await
return "This is synchronous";
}
synchronousFunction().then(console.log); // "This is synchronous"
// 使用 await 的 async 函数
// await 使得 asynchronousFunction 在等待 Promise 解决之前不会继续执行。这提供了异步操作的一种更加直观和易于理解的方式。
async function asynchronousFunction() {
const result = await new Promise(resolve => {
setTimeout(() => {
resolve("This is asynchronous");
}, 1000);
});
return result;
}
asynchronousFunction().then(console.log); // "This is asynchronous"(在1秒后打印)
总结陈词
JavaScript 中的 Promise、await 和 async 是处理异步操作的关键概念和关键字,它们在处理异步编程时起着至关重要的作用。
让我们继续见证了 JavaScript 的博大精深,后续会更新更多内容。
💗 如果觉得内容还可以,麻烦点个关注不迷路,您的鼓励是我创作的动力。
参考链接:
阮一峰 - ES6
使用ES6的Promise完美解决回调地狱
Javascript异步编程的4种方法
Promise和async/await