专栏名称: 前端大全
分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯
目录
相关文章推荐
前端早读课  ·  【第3452期】React 开发中使用开闭原则 ·  6 小时前  
启四说  ·  启四VIP策略网站,有哪些功能?如何使用? ·  17 小时前  
启四说  ·  启四VIP策略网站,有哪些功能?如何使用? ·  17 小时前  
前端早读课  ·  【第3451期】前端 TypeError ... ·  昨天  
江苏司法行政在线  ·  宿迁司法行政人、江苏监狱戒毒民警,给您拜年啦! ·  3 天前  
江苏司法行政在线  ·  宿迁司法行政人、江苏监狱戒毒民警,给您拜年啦! ·  3 天前  
51好读  ›  专栏  ›  前端大全

构建更快的 Web 体验 - 使用 postTask 调度器

前端大全  · 公众号  · 前端  · 2024-08-14 11:50

正文

译者:@古茗科技

译文:https://juejin.cn/post/7208732065696497723

作者:@Callie

原文:https://medium.com/airbnb-engineering/building-a-faster-web-experience-with-the-posttask-scheduler-

前言

介绍了如何利用 postTask 调度器来提高网页的用户体验和响应速度,通过高效地调度任务和处理优先级来优化页面性能。使用 postTask 可以拆分长任务、预加载资源和提高页面交互性能,让页面更具响应性。同时,文章还介绍了如何在 React 中集成 postTask 调度器来执行不同模式或策略,以进一步优化网页性能。今日前端早读课文章由 @古茗科技翻译分享。

正文从这开始~~

你有没有经历过打开一个网页,在页面上点击多次才有反应?或者在轮播图上滑动图片时卡顿和不自然?虽然这种经历经常发生,但是我们可以利用工具来提高用户的体验和响应速度。高效地调度和优先处理任务可能会产生快速响应的体验和感觉迟缓之间的巨大差异。

Airbnb 一直在与 Chrome 团队合作,利用优先级 postTask 调度器来实现新的模式,并提高现有模式的性能,以提高性能。在许多性能方面的努力集中在页面的初始加载上,Airbnb 的目标是提高页面加载后的用户体验。他们在许多方面使用 postTask 调度器,包括预加载轮播图中的图像和使地图更具响应性。

初识 postTask 调度器

postTask 调度器旨在为我们提供更灵活和强大的方式,以高效地调度任务。类似于 requestIdleCallback 和 setTimeout,有效地使用 postTask 调度器可以帮助减少总阻塞时间、FCP、输入延迟和其他关键指标。

在许多情况下,页面的性能不仅仅取决于初始加载的速度,而是取决于页面的响应速度和交互性能。通过使用 postTask 调度器,我们可以更好地管理任务和处理优先级,从而优化网页的性能。例如,在处理轮播图时,我们可以使用 postTask 调度器将图像预加载任务放入低优先级队列中,以确保关键任务得到优先处理。类似地,在处理地图时,我们可以使用 postTask 调度器来确保关键任务得到优先处理,从而提高地图的响应速度和交互性能。

Airbnb 为了评估他们的进展,创建了新的实时用户监测性能指标,并利用 WebPageTest 和 Lighthouse 等工具提供的现有实验室基准测试指标。

优化前(加载搜索结果页,总的阻塞时间大约为 16s 左右)

优化后 (总的阻塞时间缩短了 10s 左右)

postTask 调度器是什么

与 requestAnimationFrame、setTimeout 或 requestIdleCallback 类似,scheduler.postTask 允许我们在浏览器的事件循环中安排一个函数。然后浏览器会对该函数进行优先级排序并运行它。

注:微任务(microtask)' 和不要暂停(don't yield)。这两个优先级可能会与调度和提高应用程序的响应能力的目标背道而驰。
微任务是一小部分代码,会在当前任务完成后立即执行。它们被优先执行,可能会导致其他计划任务的延迟。不要暂停是一种优先级,用于长时间运行的任务,这些任务在执行过程中不应中断或暂停。这也可能会导致其他计划任务的延迟。
虽然这些优先级可以帮助开发人员管理任务的执行顺序,但它们也可能会导致响应能力降低和调度问题。因此,开发人员需要在使用这些优先级时与提高应用程序响应能力的整体目标之间取得平衡。*

最新版本的 chrome 浏览器已经支持了 scheduler api,对于那些不支持的浏览器也可以使用 https://www.npmjs.com/package/scheduler-polyfill 这个补丁

 scheduler.postTask(() => console.log('Hello, postTask'));

// We’re also able to pass a set of options such as priority or
// delay to influence when it will be scheduled to run.
scheduler.postTask(() => console.log('Hello, postTask'), {
delay: 1000,
priority: 'background',
});

在上面的例子中,我们向 postTask 传递了一个延迟时间和优先级参数,告诉它我们想要在等待 1 秒后在后台运行我们的任务。postTask 调度程序目前支持 3 种不同的优先级。

优先级 描述 补丁兼容版本
user-blocking 最高优先级是用于阻止用户与页面交互的任务,例如渲染核心体验或响应用户输入。 在支持的情况下,它使用 MessageChannel 尽可能快地调度任务。如果不支持,则退回到 setTimeout
user-visible 第二高优先级是用于用户可见但不一定阻止用户操作的任务,例如呈现页面的次要部分。这是默认优先级。 在支持的情况下,它也使用 MessageChannel 并退回到 setTimeout,但将排在任何具有用户阻止优先级的调用之后。
background 最低优先级是用于不是时间紧迫的任务,例如后台日志处理或初始化某些第三方库 通常使用 requestIdleCallback,并在不支持 requestIdleCallback 的情况下退回到 setTimeout (0)。

postTask 调度程序的一个好处是它建立在 Abort Signals 之上,使我们能够取消已排队但尚未执行的任务。该 API 还定义了一个新的 TaskController,它允许通过信号使用优先级来控制任务和优先级。

 const controller = new TaskController('background');
window.addEventListener('beforeunload', () => controller.abort());

scheduler.postTask(() => console.log('Hello, postTask'), {
signal: controller.signal,
});

拆解长任务

我们应该拆分长任务以提高应用程序的响应能力。下面是一个错误和行为记录上报的长任务示例。请注意浏览器如何将任务标记为长任务。

长任务(Long tasks)是指执行时间超过 50 毫秒(或者某些浏览器中可能是 100 毫秒)的任务

一旦我们确定了一个长任务,我们就可以使用 postTask 将任务分解成更小的任务。

 // By using postTask, each method will execute in its own individual task context,
// breaking the one large task into multiple smaller tasks that allow the browser
// to respond to input and do rendering in between them if necessary.
await scheduler.postTask(() => initViewportWidthProperty());
await scheduler.postTask(() => initCriticalTracking());

使用 scheduler.postTask 后,我们不再有任何长时间任务,只有小于 “长任务阈值” 的较小任务。

用例:资源预加载

预加载轮播图中的下一个图像或者在用户加载页面之前加载详细信息可以显着提高站点的性能和用户的感知性能。我们最近使用 postTask 调度程序实现了一个延迟、分阶段和可取消的图像预加载程序,用于我们的主搜索图像轮播。让我们看看如何使用 postTask 构建一个简单版本。

图片轮播预加载的触发时机:
  • 列表在屏幕上显示大约 50% 时

  • 延迟一秒;如果用户仍在查看它,则在轮播中加载下一张图片

  • 如果用户滑动图像,则预加载下三张图像,每张图片之间间隔 100ms

  • 如果轮播在一秒计时器结束之前的任何时候离开视口,我们应该取消所有尚未完成的预加载任务。如果用户导航到另一个页面,也取消所有预加载任务

当下一张幻灯片滚动到视图中时,将加载第二张图片。一旦我们滑动,接下来的 3 次加载,每次都在前一次加载后 100 毫秒开始

让我们首先看一下这个问题的第一部分,即用户将卡片滚动到视图中一半以上且维持一秒钟以上,则预加载轮播中的下一张图像。虽然在接下来的几个示例中我们使用 React,但这并非必需的。这里所有的概念也可以使用其他框架,甚至你也可以不用任何框架。

我们假设有一个名为 preloadImages 的方法,它开始获取下一张图片并在完成预加载图片时切换一个布尔值。

 const [hasPreloadedNextImage, setHasPreloadedNextImage] = useState(false);
const preloadImages = useCallback((imageUrls) => {
imageUrls.forEach((url) => preloadImage(url))
setHasPreloadedNextImage(true);
}, []);

我们可以将 Intersection Observer 和 postTask 调度程序相结合,实现在视图中 50% 一秒后加载第二张图像。

 const controller = useRef<TaskController | null>(null);
const [carouselDomRef, carouselIsInView] = useInView({
skip: hasPreloadedNextImage,
threshold: 0.5,
});

useEffect(() => {
if (carouselIsInView) {
controller.current = new TaskController('background');
scheduler.postTask(() => preloadImages([cardPhotoUrls[1]]), { delay: 1000, signal: controllerRef.current?.signal });
} else {
controller.current?.abort();
controller.current = null;
}
}, [carouselIsInView, preloadImages]);

这里我们使用了 useInView 用于检测元素是否在视图中。我们设置了一个阈值为 0.5 ,这意味着元素的一半必须在视图中才会被视为 “可见”。我们还设置了 skip 属性,以便在我们预加载下一张图片时跳过这个元素。

当元素进入视图时,我们创建了一个新的 TaskController ,用于控制预加载任务的优先级。然后,我们使用 postTask 调度程序调用 preloadImages,预加载下一张图片。我们设置了一个延迟参数为 1000ms,这意味着用户必须在视图中至少停留 1 秒钟,然后才会开始预加载下一张图片。我们还将 TaskController 的信号传递给 postTask,以便在用户滚动出视图时可以取消预加载任务。

当元素不再在视图中时,我们使用 TaskController 的 abort 方法取消任何挂起的预加载任务。

将网络资源分阶段载入

我们需要实现的最后一个要求是,在用户滑动轮播图后,每个图像请求之间间隔 100 毫秒。让我们看看如何使用 postTask 调度程序修改现有代码以应对这种情况。首先,让我们添加一个 hook,在用户与之交互时调用我们的预加载逻辑,以预加载三个图像。我们将跳过第一张图像,因为我们已经加载了它。

 useEffect(() => {
if (hasInteractedWithCarousel) {
preloadImages(imageUrls.slice(1, 4));
}
}, [hasInteractedWithCarousel]);

// We use the list index combined with delay to
// stagger the call to preload each image by 100ms each.
const preloadImages = useCallback((imageUrls) => {
imageUrls.forEach((url, index) => {
scheduler.postTask(() => preloadImage(url), {
delay: index * 100,
signal: controller.current.signal,
});
});

setHasPreloadedNextImages(true);
}, []);

postTask 调度程序的一个目标是提供一个低级别的 API,以便在其之上构建。我们已经构建了一个集成,使我们在 React 中使用时可以执行许多不同的模式或策略,我们认为这非常有用。

在 React 中使用 postTask







请到「今天看啥」查看全文