1. 前言
在 JavaScript 中,异步编程是一种处理需要等待操作(如网络请求、文件读取或计时器)的编程方式。由于 JavaScript 是单线程的,意味着它一次只能执行一个任务。异步编程允许你在等待某些操作完成时,继续执行其他任务而不会阻塞主线程。
2. 异步函数
异步函数,当你调用它时,它不会立即阻塞代码的执行。程序会立刻继续执行下面的代码。
异步的本质是因为底层机制:像 setTimeout
、Promise
、async/await
等等。这些机制让 JavaScript 不需要等待耗时操作的结果,而可以继续执行其他代码,回调函数是在异步操作完成后触发的。
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. 回调函数
回调函数是一个作为参数传递给另一个函数的函数,目的是在特定操作完成后执行。这是一种在异步编程中处理操作完成的方式。
将回调函数看作一个参数,在另一个函数中进行调用就可以很好的去理解。
在异步操作中,我们可以将回调函数当做一个通知,异步函数调用完毕后,通知操作者
javascript">function huidiao(introduce)
{let name = 'Bob';introduce(name); //在此函数中调用
};function introduce(name)
{console.log(name);
};
javascript">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. 箭头函数
javascript">(result) => {// 函数体
}
其中result代表函数所接受的参数
{}中运行的结构自动作为返回值
eg:
javascript">const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出:5
5. Promise类型
5.1 创建Promise
javascript">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类型。
javascript">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返回值
javascript">fetchPromise.then((response) => response.json()).then((data) => {console.log(data[0].name);});
与response所结合完成我们的异步操作
上述代码所示,两个then也是同步所进行的。
javascript">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 链的末尾,它就可以在任何异步函数失败时被调用。于是,我们就可以将一个操作实现为几个连续的异步函数调用,并在一个地方处理所有错误。
javascript">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是一个列表。
注意:处理过程是同步运行过程
当有一个异步未完成时,抛出错误。
javascript">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语法糖可以更加方便快捷的去创建异步函数
在函数内部也是通过同步的形式进行运行,但与主线程的工作不相互冲突。
javascript">async function OK()
{//异步函数
}
定义好异步函数之后,可以在转换线程的代码前声明await,这使得代码在该点上等待,直到 Promise 被完成,这时 Promise 的响应被当作返回值,或者被拒绝的响应被作为错误抛出。
javascript">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的
javascript">async function yibu()
{ const promises = [program1(),program2(),program3()];for wait (let I of promises){}
}
7. 小例子:闹钟警报器
7.1 基于setTimeout函数
javascript"><!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
javascript"><!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 以防止继续执行后面的代码}// 设置定时器来触发 resolvewindow.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
函数用于标记异步操作的成功完成,并且可以传递成功的结果。
javascript">const myPromise = new Promise((resolve, reject) => {// 模拟异步操作setTimeout(() => {const success = true; // 假设操作成功if (success) {resolve('操作成功'); // 将 Promise 状态变为已解决,并传递结果} else {reject('操作失败'); // 将 Promise 状态变为已拒绝,并传递错误}}, 1000);
});
8. 参考资料
如何实现基于 Promise 的 API - 学习 Web 开发 | MDN