Trong bài viết ngày hôm nay, chúng ta sẽ tạo một kết nối đồng bộ giữa Web Worker và Main Thread.

Kết nối bất đồng bộ

Để tăng hiệu năng của các website, một kỹ thuật sử dụng Web Worker để giảm bớt thời gian thực thi trên Main Thread. Sau khi Web Worker được tạo ra, Web Worker và Main Thread có thể tương tác với nhau thông qua cơ chế postMessageonMessage .

Chúng ta sẽ cùng xem xét một ví dụ sau: Trong ví dụ dưới đây, chúng ta tạo ra một Web Worker, Web Worker này sẽ gửi message tới Main Thread để lấy các thông tin về trang web hiện tại, cụ thể là các thông tin về header, và title của website hiện tại.

Web Worker được khởi tạo từ Main Thread như sau

// index.html
<head>
  <title>Hello</title>
</head>
<body>
  <div>this is main thread</div>
  <script>
    const worker = new Worker("./worker.js");
  </script>
</body>

Web Worker và Main Thread có thể tương tác với nhau thông qua postMessageonmessage như sau. Khi Worker cần thông tin từ Main Thread, Worker sẽ gửi message tới Main Thread, sau đó Main Thread sẽ trả về một response tới Worker. Khi Worker nhận message từ Main Thread, để có thể biết được message đó là gắn với request nào từ trước, mỗi khi Worker gửi request, Worker sẽ gửi theo một request id, và khi Main Thread trả về result, Main Thread sẽ gửi kèm cả request id này trong response.

// worker.js 

const callbacks = {};

self.onmessage = (e) => {
  const data = e.data;
  if (!data.requestId) {
    console.log("receive invalid message");
    return;
  }

  const { requestId, result, error } = data;
  if (!callbacks[requestId]) {
    console.log("invalid requestId", requestId);
    return;
  }

  const callback = callbacks[requestId];
  callback({ result, error });
  delete callbacks[requestId];
};

function generateRequestId() {
  return Date.now() + "" + Math.floor(Math.random() * 1000000);
}

function sendToMainThread({ message, params, callback }) {
  const requestId = generateRequestId();
  callbacks[requestId] = callback;
  self.postMessage({
    requestId,
    message,
    params,
  });
}

Chúng ta tạo ra hàm sendToMainThread, hàm này có thể được sử dụng như sau

// worker.js
async function getWindowTitle() {
  return new Promise((resolve, reject) => {
    sendToMainThread({
      message: "getWindowTitle",
      params: {},
      callback: (data) => {
        if (data.error) {
          reject(error);
          return;
        }
        resolve(data.result);
      },
    });
  });
}

Trong ví dụ trên, chúng ta tạo ra API getWindowHeader . API này trả về một Promise để lấy thông tin về title của window trên Main Thread. Hàm getWindowTitle được gọi trong Worker như sau

(async function main() {
  const title = await getWindowTitle();
  console.log("title is", title);
})();

Hàm này được implement trên Main Thread như sau

worker.onmessage = (e) => {
  const { requestId, message, params } = e.data;
  if (message === "getWindowTitle") {
    const title = getWindowTitle();
    worker.postMessage({
      requestId,
      result: title,
    });
    return;
  }
};

function getWindowTitle() {
  return document.title;
}