使用代码延迟任务执行
为了拆分长任务,开发者经常使用定时器 setTimeout。通过把方法传递给 setTimeout,也就等同于重新创建了一个新的任务,延迟了回调的执行,而且使用该方法,即便是将 delay 时间设定成 0,也是有效的。
缺点: 在需要循环中大量处理数据,(假设有百万数据)这个任务耗时就会非常长
function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Defer work that isn't user-visible to a separate task:
setTimeout(() => {
saveToDatabase();
sendAnalytics();
}, 0);
}
除了 setTimeout 的方式,确有一些 api 能够允许延迟代码到随后的任务中执行。其中一个方式便是使用 postMessage 替代定时器;也可以使用 requestIdleCallback,但是需要注意这个 api 编排的任务的优先级别最低,而且只会在浏览器空闲时才会执行。当主线程繁忙时,通过 requestIdleCallback 这个 api 编排的任务可能永远不会执行。
使用async await创建让步点
当让步于主线程后,浏览器就有机会处理那些更重要的任务,而不是放在队列中排队。理想状态下,一旦出现用户界面级别的任务,就应该让步给主线程,让任务更快的执行完。让步于主线程让更重要的工作能更快的完成
function yieldToMain () {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function saveSettings () {
// Create an array of functions to run:
const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ]
// Loop over the tasks:
while (tasks.length > 0) {
// Shift the first task off the tasks array:
const task = tasks.shift();
// Run the task:
task();
// Yield to the main thread:
await yieldToMain();
}
}
必要时让步
比如说,任务队列中有很多任务,但是不想阻挡用户输入,使用 isInputPending 和自定义方法 yieldToMain 方法,就能够保证用户交互时的 input 不会延迟。
浏览器支持该策略
async function saveSettings () {
// 函数队列
const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ];
while (tasks.length > 0) {
// 让步于用户输入
if (navigator.scheduling.isInputPending()) {
// 如果有用户输入在等待,则让步
await yieldToMain();
} else {
// Shift the the task out of the queue:
const task = tasks.shift();
// Run the task:
task();
}
}
}
浏览器不支持,可以结合时间方式
async function saveSettings () {
// A task queue of functions
const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ];
let deadline = performance.now() + 50;
while (tasks.length > 0) {
// Optional chaining operator used here helps to avoid
// errors in browsers that don't support `isInputPending`:
if (navigator.scheduling?.isInputPending() || performance.now() >= deadline) {
// There's a pending user input, or the
// deadline has been reached. Yield here:
await yieldToMain();
// Extend the deadline:
deadline += 50;
// Stop the execution of the current loop and
// move onto the next iteration:
continue;
}
// Shift the the task out of the queue:
const task = tasks.shift();
// Run the task:
task();
}
}
专门编排优先级的 api
该 api 提供 postTask 的功能,目前兼容性不太高
https://developer.mozilla.org/en-US/docs/Web/API/Scheduler/postTask