How React Fiber Leverages requestIdleCallback: Deep Dive & DIY Implementation

This article explains the concept of requestIdleCallback, its limitations, and how React Fiber implements a custom idle‑time scheduler using requestAnimationFrame and MessageChannel, complete with code demos and analysis of the underlying React source.

ELab Team
ELab Team
ELab Team
How React Fiber Leverages requestIdleCallback: Deep Dive & DIY Implementation

1. Introduction

Elab Juejin: React Fiber architecture shallow analysis. React internally uses requestIdleCallback to run low‑priority work during a frame’s idle time, but its Scheduler + Lane model is far more complex.

2. requestIdleCallback

2.1 Concept

Figure: Simple frame lifecycle description.

RequestIdleCallback determines whether a frame has idle time and, if so, executes a given task.

The goal is to prevent long‑running tasks from blocking higher‑priority work such as animations, which would cause frame drops.

It targets tasks that are not important and not urgent .

Signature:

window.requestIdleCallback(callback[, options]); the callback receives a deadline object.

// Callback receives a deadline object
type Deadline = {
  timeRemaining: () => number // remaining time in the current frame
  didTimeout: boolean // whether the callback timed out
}

// requestIdleCallback type definition
type RequestIdleCallback = (cb: (deadline: Deadline) => void, options?: Options) => number

2.2 Demo

Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/

GitHub repository: RequestIdleCallback experiment.

const bindClick = id => {
  element(id).addEventListener('click', Work.onAsyncUnit)
}

bindClick('btnA')
bindClick('btnB')
bindClick('btnC')

var Work = {
  unit: 10000,
  onOneUnit: function () { for (var i = 0; i <= 500000; i++) {} },
  onAsyncUnit: function () {
    const FREE_TIME = 1
    let _u = 0
    function cb(deadline) {
      while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) {
        Work.onOneUnit()
        _u++
      }
      if (_u >= Work.unit) return
      window.requestIdleCallback(cb)
    }
    window.requestIdleCallback(cb)
  }
}

2.3 Drawbacks

Experimental API with limited browser support.

Only about 20 calls per second, which is slower than the 60 Hz frame rate (16.67 ms per frame) needed for smooth UI.

React’s rendering work is not “unimportant or non‑urgent”, so the React team built its own scheduler instead of relying on this API.

3. React requestIdleCallback implementation experiment

Two key questions: when can a frame be considered idle, and where within the frame should the idle work run?

3.1 Using requestAnimationFrame to compute frame deadline

requestAnimationFrame is driven by the browser’s refresh cycle (typically 60 Hz). It groups DOM changes and paints them in a single frame, ensuring no frame drops.

// requestAnimationFrame receives the start time of the frame
type RequestAnimationFrame = (cb: (rafTime: number) => void)

By adding the typical frame duration (16.667 ms) to the start time, we obtain the deadline for that frame.

var deadlineTime;
window.selfRequestIdleCallback = function(cb) {
  requestAnimationFrame(rafTime => {
    deadlineTime = rafTime + 16.667;
    // ...
  })
}

3.2 Using MessageChannel for macro‑tasks

MessageChannel creates a two‑port communication pipe; posting a message schedules a macro‑task.

Why macro‑tasks instead of micro‑tasks? Micro‑tasks run before the browser can paint, so they cannot yield control back to the browser.

If MessageChannel is unavailable, setTimeout is used as a fallback, but it has a minimum delay of about 4 ms.

var i = 0;
var _start = +new Date();
function fn() {
  setTimeout(() => {
    console.log('execution count, time', ++i, +new Date() - _start);
    if (i === 10) return;
    fn();
  }, 0);
}
fn();

Implementation using MessageChannel:

var deadlineTime;
var callback;
var channel = new MessageChannel();
var port1 = channel.port1;
var port2 = channel.port2;

port2.onmessage = () => {
  const timeRemaining = () => deadlineTime - performance.now();
  const _timeRemain = timeRemaining();
  if (_timeRemain > 0 && callback) {
    const deadline = { timeRemaining, didTimeout: _timeRemain < 0 };
    callback(deadline);
  }
};

window.requestIdleCallback = function(cb) {
  requestAnimationFrame(rafTime => {
    deadlineTime = rafTime + 16.667;
    callback = cb;
    port1.postMessage(null);
  });
};

4. React source: requestHostCallback

In SchedulerHostConfig.js, requestHostCallback triggers the macro‑task performWorkUntilDeadline, which processes work while there is time left in the current frame.

let scheduledHostCallback = null;
let isMessageLoopRunning = false;
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;

const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    const deadline = currentTime + yieldInterval;
    const hasTimeRemaining = true;
    try {
      const hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
      if (!hasMoreWork) {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      } else {
        port.postMessage(null);
      }
    } catch (error) {
      port.postMessage(null);
      throw error;
    }
  } else {
    isMessageLoopRunning = false;
  }
  needsPaint = false;
};

requestHostCallback = function(callback) {
  scheduledHostCallback = callback;
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    port.postMessage(null);
  }
};

cancelHostCallback = function() {
  scheduledHostCallback = null;
};

References

Elab Juejin: React Fiber architecture shallow analysis – https://juejin.cn/post/7005880269827735566

window.requestIdleCallback – https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

RequestIdleCallback experiment – https://github.com/Linjiayu6/FE-RequestIdleCallback-demo

Discussion on requestIdleCallback frequency – https://github.com/facebook/react/issues/13206#issuecomment-418923831

requestAnimationFrame – https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

DOMHighResTimeStamp – https://developer.mozilla.org/zh-CN/docs/Web/API/DOMHighResTimeStamp

MessageChannel – https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel/MessageChannel

SchedulerHostConfig.js – https://github.com/facebook/react/blob/v17.0.1/packages/scheduler/src/forks/SchedulerHostConfig.default.js

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptReactSchedulerFiberfrontend performanceMessageChannelrequestIdleCallback
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.