异步JS
浏览器中的Event Loop事件循环
- JS是单线程的,异步事件依赖于事件循环机制
- js主线程在执行栈中执行同步任务,如果遇到异步任务,会在幕后线程中执行。
- 当异步任务执行完成,会将回调函数放到任务队列等待执行
- 当执行栈中的所有同步任务执行完毕,就会执行任务队列中的任务。
- 如果当前任务队列为空的话,它就会一直循环等待任务到来,重复获取任务执行任务的过程,这种机制被称为事件循环。
任务队列
- 宏任务队列:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI交互事件、postMessage、MessageChannel;一次事件循环只执行位于宏任务队首的任务,执行完成后立即执行微任务队列中的所有任务(一开始在js主线程中跑的任务就是宏任务,因此执行完主线程的代码后,会从Microtask队列中取任务来执行)
- 微任务队列:Promise.then、Object.observe、await;宏任务完成后,Event Loop会从微任务队列中取出所有的任务,按照添加的顺序依次执行直到队列为空。
- 定时器触发器队列:setTimeout和setInterval生成的事件指令的队列。Event Loop在定时器到期后,将处理程序放入适当的宏任务队列中。
- setTimeout在指定的延迟后执行一次函数,不保证可靠定时,如果主线程忙碌或函数执行时间过长,setTimeout的调用可能会被推迟;而setInterval在指定的间隔后执行函数,无论主线程的忙碌程度如何,如果函数执行时间超过指定间隔,setInterval不会等待上一次调用的完成。
在定义promise的时候,promise构造部分是同步执行的
Promise
- 异步函数总是返回一个Promise
- fetch() API 获取Response对象,返回值赋给Promise对象,操作成功时,Promise 将调用then()方法
- Promise 链:连续调用异步函数,比如Response对象的json()方法,由于then()方法也会返回promise对象,可以直接在返回值上调用第二个 “then()”
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
fetchPromise
.then((response) => {
return response.json();
})
.then((json) => {
console.log(json[0].name);
});
- 错误处理:当异步操作失败时,传递给catch()的处理函数被调用。如果将 catch() 添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。
- fetch() 认为服务器返回一个错误(如404 Not Found)时请求成功,但如果网络错误阻止请求被发送,则认为请求失败。
- Promise状态:待定(pending)、已兑现(fulfilled)、已拒绝(rejected)
合并Promise:
- Promise.all()方法接收一个 Promise 数组,并返回一个单一的 Promise。
当且仅当数组中所有的 Promise 都被兑现时,才会通知 then() 处理函数并提供一个包含所有响应的数组,数组中响应的顺序与被传入 all() 的 Promise 的顺序相同。
如果数组中有任何一个 Promise 被拒绝。此时,catch() 处理函数被调用,并提供被拒绝的 Promise 所抛出的错误。
Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
.then((responses) => {
for (const response of responses) {
console.log(`${response.url}:${response.status}`);
}
})
.catch((error) => {
console.error(`获取失败:${error}`);
});
- Promise.any()方法当Promise 数组中的任何一个被兑现时,它就会被兑现,只有所有的 Promise 都被拒绝,它才会被拒绝。
async 和 await
- async:在一个函数的开头添加 async,就可以使其成为一个异步函数。
- await:异步函数中,在调用一个返回 Promise 的函数之前使用 await 关键字。这使得代码在该点上等待,直到 Promise 被完成,这时 Promise 的响应被当作返回值。
- await强制异步操作以串联的方式完成,类似Promise链。如果下一个操作的结果取决于上一个操作的结果,这是必要的。
async function fetchProducts() {
try {
// 在这一行之后,我们的函数将等待 `fetch()` 调用完成
// 调用 `fetch()` 将返回一个“响应”或抛出一个错误
const response = await fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
if (!response.ok) {
throw new Error(`HTTP 请求错误:${response.status}`);
}
// 在这一行之后,我们的函数将等待 `response.json()` 的调用完成
// `response.json()` 调用将返回 JSON 对象或抛出一个错误
const json = await response.json();
console.log(json[0].name);
} catch (error) {
console.error(`无法获取产品列表:${error}`);
}
}
基于 promise 的 alarm API
- setTimeout(callfunction, ms):启动一个设置为给定延迟的计时器,当时间过期时,它就会调用给定的回调函数。
- Promise构造函数:Promise() 构造器使用单个函数作为参数,该函数的两个参数分别是resolve和reject;resolve在异步操作成功时调用,reject在异步操作失败时调用。
function alarm(person, delay) {
return new Promise((resolve, reject) => {
if (delay < 0) {
throw new Error("Alarm delay must not be negative");
}
window.setTimeout(() => {
resolve(`Wake up, ${person}!`);
}, delay);
});
}
返回的promise中可以调用then()和catch()来设置 promise 兑现和拒绝状态的处理器。
button.addEventListener("click", () => {
alarm(name.value, delay.value)
.then((message) => (output.textContent = message))
.catch((error) => (output.textContent = `Couldn't set alarm: ${error}`));
});
- 改进:对返回的Promise使用async和await:
button.addEventListener("click", async () => {
try {
const message = await alarm(name.value, delay.value);
output.textContent = message;
} catch (error) {
output.textContent = `Couldn't set alarm: ${error}`;
}