1. 前言
在 JavaScript 中,异步编程是一种处理需要等待操作(如网络请求、文件读取或计时器)的编程方式。由于 JavaScript 是单线程的,意味着它一次只能执行一个任务。异步编程允许你在等待某些操作完成时,继续执行其他任务而不会阻塞主线程。
2. 异步函数
异步函数,当你调用它时,它不会立即阻塞代码的执行。程序会立刻继续执行下面的代码。
异步的本质是因为底层机制:像 setTimeout
、Promise
、async/await
等等。这些机制让 JavaScript 不需要等待耗时操作的结果,而可以继续执行其他代码,回调函数是在异步操作完成后触发的。
console.log("I'm going to make dinner");
setTimeOut(()=>{Console.log('My dinner is ready');},1000);
console.log('I'm going to watch TV');
//I'm going to make dinner
//I'm going to watch TV
//My dinner is ready
3. 回调函数
回调函数是一个作为参数传递给另一个函数的函数,目的是在特定操作完成后执行。这是一种在异步编程中处理操作完成的方式。
将回调函数看作一个参数,在另一个函数中进行调用就可以很好的去理解。
在异步操作中,我们可以将回调函数当做一个通知,异步函数调用完毕后,通知操作者
function huidiao(introduce)
{
let name = 'Bob';
introduce(name); //在此函数中调用
};
function introduce(name)
{
console.log(name);
};
function doStep1(init, callback) {
const result = init + 1;
callback(result);
}
function doStep2(init, callback) {
const result = init + 2;
callback(result);
}
function doStep3(init, callback) {
const result = init + 3;
callback(result);
}
function doOperation() {
doStep1(0, (result1) => {
doStep2(result1, (result2) => {
doStep3(result2, (result3) => {
console.log(`结果:${result3}`);
});
});
});
}
doOperation();
doOperation
函数依次调用doStep1
doStep2
和doStep3
,并在每一步完成后,将结果传递给下一步的回调函数。
在上述代码中,(result1) ==> {} 表示回调函数,在JavaScript独有的箭头函数
4. 箭头函数
(result) => {
// 函数体
}
其中result代表函数所接受的参数
{}中运行的结构自动作为返回值
eg:
const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出:5
5. Promise类型
5.1 创建Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve('成功的结果');
} else {
reject('失败的原因');
}
});
使用我们的异步函数,最后异步函数所返回的类型为Promise类型
首先,Promise 有三种状态:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。这是调用
fetch()
返回 Promise 时的状态,fetch()是一个异步函数,不干扰主线程,此时请求还在进行中。 - 已兑现(fulfilled):意味着操作成功完成。当 Promise 完成时,
then()
处理函数(类似于我们的回调函数,异步任务处理完成后)被调用。 - 已拒绝(rejected):意味着操作失败。当一个 Promise 失败时,它的
catch()
处理函数被调用。catch函数进行处理错误。
5.2 fetch()函数
使用fetch异步函数,会得到Promise返回对象
Promise返回对象:Promise { <state>: "pending" }
。这告诉我们有一个 Promise
对象,它有一个 state
属性,值是 "pending"
。"pending"
状态意味着操作仍在进行中。所有的异步函数返回对象都是Promise类型。
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
console.log(fetchPromise);
fetchPromise.then((response) => {
console.log(`已收到响应:${response.status}`);
});
console.log("已发送请求……");
上述代码使用fetch函数,将内容获取操作不占用主线程,仍然可以继续运行主线程的代码。
5.3 .then
获取到返回的Promise对象之后,使用Promise对象参数then,类似于我们的回调函数,异步操作完成之后我们需要干什么的编写。
promise.then会判断异步操作成功后,自动向回调函数传入response返回值
fetchPromise
.then((response) => response.json())
.then((data) => {
console.log(data[0].name);
});
与response所结合完成我们的异步操作
上述代码所示,两个then也是同步所进行的。
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
fetchPromise.then((response) => {
const jsonPromise = response.json();
jsonPromise.then((json) => {
console.log(json[0].name);
});
});
5.4 .catch
你调用它并传入一个处理函数。然后,当异步操作成功时,传递给 then()
的处理函数被调用,而当异步操作失败时,传递给 catch()
的处理函数被调用。
如果将 catch()
添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。于是,我们就可以将一个操作实现为几个连续的异步函数调用,并在一个地方处理所有错误。
const fetchPromise = fetch(
"bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
fetchPromise
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP 请求错误:${response.status}`);
}
return response.json();
})
.then((json) => {
console.log(json[0].name);
})
.catch((error) => {
console.error(`无法获取产品列表:${error}`);
});
5.5 .final
无论获取是正确还是错误,异步操作仍然执行的部分。
5.6 Promise.all()
处理多个异步函数时可以用到。
当所有的异步函数处理完毕后,使用Promise.all(异步函数返回对象),对所有的返回对象做进一步处理,通常搭配循环,返回的responses是一个列表。
注意:处理过程是同步运行过程
当有一个异步未完成时,抛出错误。
const fetchPromise1 = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
const fetchPromise2 = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/not-found",
);
const fetchPromise3 = fetch(
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
);
Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
.then((responses) => {
for (const response of responses) {
console.log(`${response.url}:${response.status}`);
}
})
.catch((error) => {
console.error(`获取失败:${error}`);
});
6. async&await关键字
使用async语法糖可以更加方便快捷的去创建异步函数
在函数内部也是通过同步的形式进行运行,但与主线程的工作不相互冲突。
async function OK()
{
//异步函数
}
定义好异步函数之后,可以在转换线程的代码前声明await,这使得代码在该点上等待,直到 Promise 被完成,这时 Promise 的响应被当作返回值,或者被拒绝的响应被作为错误抛出。
async function time_end
{
try {
const response = await fetch('url');
}
const json = response.json();
catch(error){console.error(`${error}`)};
}
await fetch所返回的是一个完整的response对象而不是promise对象。
await相当于自动将Promise对象转为response对象
await
强制异步操作以串联的方式完成。如果下一个操作的结果取决于上一个操作的结果,这是必要的,但如果不是这样,像 Promise.all()
这样的操作会有更好的性能。
在异步函数中使用循环
注意:在异步函数中是不能使用foreach的
async function yibu()
{
const promises = [program1(),program2(),program3()];
for wait (let I of promises)
{
}
}
7. 小例子:闹钟警报器
7.1 基于setTimeout函数
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<button id='set-alarm'>Set alarm</button>
<div id='output'></div>
</body>
<script>
const output = document.querySelector('#output');
const button = document.querySelector('#set-alarm');
function setAlarm()
{
window.setTimeout(()=>{
output.textContent = 'Wake up';
},2000);
}
button.addEventListener("click", setAlarm);
</script>
</html>
7.2 基于async&await
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
Name: <input type='text' id='name'>
<br>Delay: <input type='number' id='delay'>
<button id='set-alarm'>Set alarm</button>
<div id='output'></div>
</body>
<script>
const output = document.querySelector('#output');
const button = document.querySelector('#set-alarm');
const delay = document.querySelector('#delay');
const name = document.querySelector('#name');
function alarm(person, delay) {
return new Promise((resolve, reject) => { //函数返回一个Promise对象
if (delay < 0) {
reject(new Error('Alarm delay must not be negative'));
return; // 需要 return 以防止继续执行后面的代码
}
// 设置定时器来触发 resolve
window.setTimeout(() => {
resolve(`Wake up, ${person}!`);
}, delay); // 注意这里没有括号错位
});
}
button.addEventListener('click', async () => {
try {
const message = await alarm(name.value, parseInt(delay.value)); // 确保 delay.value 是数字
output.textContent = message;
} catch (error) {
output.textContent = error.message; // 如果发生错误,显示错误信息
}
});
</script>
</html>
7.3 resolve参数
通常与reject参数一起搭配使用
它是 Promise
对象的构造函数中的一个参数。resolve
的作用是将 Promise
对象的状态从 待定(pending) 变为 已解决(fulfilled)。这意味着 resolve
函数用于标记异步操作的成功完成,并且可以传递成功的结果。
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true; // 假设操作成功
if (success) {
resolve('操作成功'); // 将 Promise 状态变为已解决,并传递结果
} else {
reject('操作失败'); // 将 Promise 状态变为已拒绝,并传递错误
}
}, 1000);
});
8. 参考资料
如何实现基于 Promise 的 API - 学习 Web 开发 | MDN