目录
一、异步编程介绍
1.概念
异步编程是一种编程模式,用于处理程序中需要长时间等待的操作,比如网络请求、文件读写、定时器等。在传统的同步编程模式下,程序会按照顺序一步步执行,如果遇到需要耗时的操作,程序会阻塞等待操作完成,直到操作完成后才能继续执行后续代码。
2.实现方式
在JavaScript中,异步编程有多种实现方式,包括:
-
回调函数(Callbacks):最早的异步编程模式是使用回调函数,通过将耗时操作放在回调函数中,在操作完成后调用回调函数来处理结果。这种方式简单直接,但容易产生回调地狱(Callback Hell)问题,代码不易维护。
-
Promise对象:Promise是ES6引入的一种处理异步操作的对象,它表示一个异步操作的最终完成或失败,并且可以链式调用then方法处理操作结果,避免了回调地狱的问题。
-
async/await:async/await是建立在Promise基础之上的语法糖,它提供了一种更加简洁和直观的方式来编写异步代码。通过async关键字定义异步函数,在函数内部使用await关键字等待异步操作的结果,使得异步代码看起来像同步代码一样,易于理解和维护。
3.利与弊
异步编程可以提高程序的响应性能和用户体验,特别是在处理大量IO操作的情况下。然而,过度的异步编程也可能会导致代码复杂性增加,因此需要根据具体情况权衡利弊,选择适合的异步编程模式。
4.什么时候会用到异步编程
在前端编程中(甚至后端有时也是这样),我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,界面将无法处理其他请求。
现在有一个按钮,如果我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。
为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。
为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。
二、async-await异步处理模式
1.概念
async/await是JavaScript中处理异步操作的一种方式,它是建立在Promise基础之上的语法糖,使得异步代码的编写更加简洁和直观。在介绍async/await模式之前,我们先来了解一下Promise对象的基本概念。
2.Promise对象
Promise对象用于表示一个异步操作的最终完成或失败。它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise对象可以通过then方法添加成功(fulfilled)和失败(rejected)的回调函数,也可以通过catch方法捕获异常。
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(result); // 成功,将结果传递给then方法
} else {
reject(error); // 失败,将错误传递给catch方法
}
});
promise.then((result) => {
// 成功时的回调函数
}).catch((error) => {
// 失败时的回调函数
});
3.async/await模式
async/await是在ES2017中引入的语法糖,它使得异步代码的编写更加直观和易于理解。async函数用于定义一个异步函数,函数内部可以使用await关键字等待Promise对象的解决或拒绝。await关键字会暂停函数的执行,直到Promise对象状态发生改变。
async function myAsyncFunction() {
try {
const result = await someAsyncOperation();
console.log(result);
} catch (error) {
console.error(error);
}
}
myAsyncFunction();
在上面的示例中,myAsyncFunction是一个异步函数,内部使用await关键字等待someAsyncOperation函数返回的Promise对象。当Promise对象解决时,结果会被赋值给result变量;如果Promise对象被拒绝,则会抛出异常并被catch语句捕获。
4.主要优点
- 更直观的异步编程:async/await模式使得异步代码的编写更像是同步代码,易于理解和维护,避免了传统的Promise链式调用带来的回调地狱问题。
- 错误处理更简洁:使用try...catch语句可以捕获异步操作中的异常,使得错误处理更加直观和简洁。
5.注意事项
- 只能在async函数内部使用await:await关键字只能在async函数内部使用,否则会导致语法错误。
- async函数返回Promise对象:async函数总是返回一个Promise对象,可以通过then方法添加成功和失败的回调函数。
async/await模式简化了异步编程的复杂度,使得开发者可以更加专注于业务逻辑的实现。然而,需要注意的是,过度使用async/await也可能会导致性能问题,特别是在处理大量并发请求的情况下,因此需要根据实际情况进行权衡和优化。
三、异步函数具体使用
1.异步代码执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>按钮</button>
<script>
const btn = document.querySelector("button")
btn.onclick = function(){
console.log("btn click event")
}
console.log("Hello World!")
let message = "aaaaaa"
message = "bbbbb"
setTimeout(() => {
console.log("10s后的setTimeout")
}, 10000);
console.log("Hello JavaScript")
console.log("代码会继续运行~")
console.log("--------")
</script>
</body>
</html>
2.异步函数结构
// 普通函数
// function foo(){}
// const bar = function(){}
// const baz = () =>{}
// 生成器函数
// function* foo(){}
//异步函数
async function foo() {
console.log("foo function1")
console.log("foo function2")
console.log("foo function3")
}
foo()
const bar = async function () { }
const baz = async () => { }
class Person {
async running() { }
}
3.异步函数异常
//"abc".filter()
//什么情况下异步函数的结果是rejected
async function foo() {
console.log("-------1")
console.log("-------2")
"abc".filter()
console.log("-------3")
return new Promise((res, rej) => {
rej("err rejected")
})
}
//promise -> pending ->fulfilled/rejected
foo().then(res => {
console.log("res:", res)
}).catch(err => {
console.log("err:", err)
})
4.异步函数返回值
//返回值的区别
//1.普通函数
// function foo1(){
// return 123
// }
// foo1()
//2.异步函数
async function foo2() {
//1.返回一个普通的值
// -》Promise.resolve(321)
// return 321
// return ["abe","cac","esa"]
//2.返回一个Promise
// return new Promise((res,rej) =>[
// setTimeout(() => {
// res("aaa")
// }, 3000)
// ])
//3.返回一个thenable对象
return {
then: function (res, rej) {
setTimeout(() => {
res("bbb")
}, 3000)
}
}
}
foo2().then(res => {
console.log("res:" + res)
})
5.await关键字使用
//新的关键字:await
//await条件:必须在异步函数中使用
function bar() {
return new Promise(res => {
setTimeout(() => [
res(123)
], 3000)
})
}
async function foo1() {
console.log("-------")
//await后续返回一个Promise,那么会等待Promise有结果之后,才会继续执行后续的代码
const res = await bar()
console.log("await后面的代码:", res)
const res2 = await bar()
console.log("await后面的代码:", res2)
console.log("-------")
}
foo1()
6.await处理异步请求
function requestData(url) {
return new Promise((res, rej) => [
setTimeout(() => {
// res(url)
rej("error rejected")
}, 2000)
])
}
async function getData() {
try {s
const res1 = await requestData("why")
console.log('res1:', res1)
const res2 = await requestData(res1 + " koe")
console.log("res2:", res2)
} catch (err) {
console.log("err", err)
}
}
getData()
7.await和async结合
//1.定义一些其他的异步函数
function requestData(url) {
setTimeout((url) => {
res(url)
}, 3000)
}
async function test() {
console.log("test function")
return "test"
}
async function bar() {
console.log("bar function")
return new Promise((res) => [
setTimeout(() => {
res("bar")
}, 2000)
])
}
async function demo() {
console.log("demo function ")
return {
then: function () {
res("demo")
}
}
}
async function foo() {
const res1 = await requestData("whys")
console.log("res1:", res1)
const res2 = await test()
console.log("res2:", res2)
const res3 = await bar()
console.log("res3:", res3)
const res4 = await demo()
console.log("res4:", res4)
}
foo()
8.异步请求代码结构
//封装请求的方法 : url->promise(result)
// function execCode(count){
// return new Promise((res,rej)=>{
// setTimeout(() => {
// res(count)
// }, 2000);
// })
// }
// execCode(100).then(res=>{
// console.log("res=:" ,res)
// })
function requestData(url) {
return new Promise((res, rej) => {
setTimeout(() => {
res(url)
}, 2000);
})
return data
}
//发送一次网络请求
// requestData("http://jw.com").then(res=>[
// console.log("res:",res)
// ])
//方式一:层层嵌套(回调地狱)
// function getData() {
// //第一次 请求
// requestData("why").then(res1 => {
// console.log("第一次结果:", res1)
// //第二次请求
// requestData(res1 + "xjy").then(res2 => {
// console.log("第二次结果:", res2)
// requestData(res2 + "yy").then(res3 => {
// console.log("第三次结果:", res3)
// })
// })
// })
// }
async function getData() {
const res1 = await requestData("why")
console.log("res1", res1)
const res2 = await requestData(res1 + "kove")
console.log("res2", res2)
const res3 = await requestData(res2 + "jame")
console.log("res3", res3)
}
const generator = getData()
9.生成器代码的优化
function requestData(url) {
return new Promise((res, rej) => {
setTimeout(() => {
res(url)
}, 2000);
})
return data
}
async function getData() {
const res1 = await requestData("why")
console.log("res1", res1)
const res2 = await requestData(res1 + "kove")
console.log("res2", res2)
const res3 = await requestData(res2 + "jame")
console.log("res3", res3)
const res4 = await requestData(res3 + "break")
console.log("res4", res4)
}
//自动化执行生成器函数
function execGenFn(genFn){
const generator = genFn()
function exec(res){
const result = generator.next(res)
if(result.done) return
result.value.then(res=>{
exec(res)
})
}
exec()
}
execGenFn(getData)
四、async-await应用场景
1.网络请求:在Web开发中,经常需要与服务器进行通信,比如获取数据或提交表单。使用async/await模式可以更清晰地编写异步的HTTP请求代码,而不需要使用回调函数或Promise链式调用。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
2.文件操作:读取和写入文件通常是异步操作,使用async/await可以简化文件操作的代码,使其更易读易维护。
const fs = require('fs').promises;
async function readFile() {
try {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
} catch (error) {
console.error('Error reading file:', error);
}
}
async function writeFile(content) {
try {
await fs.writeFile('example.txt', content);
console.log('File written successfully');
} catch (error) {
console.error('Error writing file:', error);
}
}
readFile();
writeFile('Hello, world!');
3.定时任务:使用定时器执行延迟操作时,async/await可以更清晰地表达代码意图。
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function performTask() {
console.log('Task started');
await delay(2000); // 等待2秒
console.log('Task completed');
}
performTask();
4.数据库操作:与数据库进行交互通常是异步操作,例如查询数据、插入数据等。async/await模式可以使数据库操作的代码更加清晰易懂。
const db = require('some-database-library');
async function fetchDataFromDB() {
try {
const result = await db.query('SELECT * FROM table');
console.log(result);
} catch (error) {
console.error('Error fetching data from database:', error);
}
}
fetchDataFromDB();
五、总结
异步编程模式是一种用于处理异步操作的编程范例,其核心目标是提高程序的效率和性能,同时保持代码简洁易读。以下是异步编程模式的主要特点和常见模式:
-
回调函数:
- 最基本的异步编程模式,通过将函数作为参数传递给异步操作,在操作完成时调用该函数。
- 容易导致回调地狱(Callback Hell),难以维护和理解,因为嵌套层级很深。
-
Promise:
- 提供了更优雅的解决方案来处理异步操作,通过返回Promise对象来表示异步操作的状态和结果。
- 允许通过链式调用.then()方法处理异步操作的结果,避免了回调地狱问题。
- Promise提供了.catch()方法来处理错误,使错误处理更加简洁。
-
async/await:
- 基于Promise的语法糖,提供了更直观、更易于理解的异步编程方式。
- 使用async函数定义异步操作,内部使用await关键字等待Promise对象的解决或拒绝。
- 可以使用try...catch语句捕获异步操作中的异常,使得错误处理更简洁。
-
事件监听:
- 将事件监听器与异步操作结合,通过订阅和触发事件来处理异步操作的完成。
- 适用于需要处理多个异步操作的情况,例如在Node.js中处理HTTP请求或处理用户交互事件。
-
生成器函数和迭代器:
- 使用生成器函数和迭代器实现异步操作的同步风格编程。
- 通过yield关键字将异步操作分解成多个步骤,使得代码更易读和维护。
- 需要配合Promise或其他异步方案一起使用。
总的来说,异步编程模式旨在解决在程序中处理异步操作时遇到的复杂性和困难,不同的模式可以根据具体情况选择最适合的方式来进行异步编程,提高代码的可读性、可维护性和性能。