iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🪔

Easily Calling Web Workers in Next.js

に公開

How to Easily Use Web Workers in Next.js

As I mentioned in my previous article, Next.js includes Webpack configurations for loading Web Workers out of the box, so they can be used as is.
However, it only provides the configuration to load scripts written as Web Workers, and there is no other support.
To use the functionality, you need to write a somewhat cumbersome procedure in your code.

Previous Article

Calling Web Workers in Next.js

Sample Repository

https://github.com/SoraKumo001/next-worker2
<https://next-worker2.vercel.app/ >

Cumbersome Points of Using Web Workers

  • You need to create a separate file for the Worker functionality.
    While there is an inline feature, it is also cumbersome in its own way.
  • Communication between the main thread and the Worker must go through postMessage and addEventListener.
    Even for simple data exchanges, this procedure makes the code less readable.
  • When trying to add TypeScript types for data exchange, you have to write them on both the Worker side and the main thread side.
    I want to avoid the hassle of writing similar code twice.

Exploring a Solution

Just eliminate postMessage and addEventListener, and allow functions created on the Worker side to be called directly from the main thread.

What a great idea!

The problem was easily solved.

What I Created

I have registered a library on npm to make it easy to use Web Workers.
https://www.npmjs.com/package/worker-lib

Sample Code

Worker Side Processing

Create five functions and use initWorker to make them available as a Worker. Then, export only the type information.

  • src/libs/worker-test.ts
import { initWorker } from "worker-lib";

const add = (a: number, b: number) => {
  for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
  return a + b
}
const add2 = (a: string, b: string) => {
  for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
  return a + b
}
const sub = (a: number, b: number) => {
  for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
  return a - b
}
const mul = (a: number, b: number) => {
  for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
  return a * b
}

const error = (a: number, b: number) => {
  for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
  throw new Error("throw")
  return  a + b;
}

// Initialization process to make it usable in Worker.
const map = initWorker({ add, add2, sub, mul, error })
// Export only the type
export type WorkerTest = typeof map

Processing for the User (Next.js)

Load worker-test using createWorker. At this time, pass the type information that was exported under the name WorkerTest. Additionally, if you set the maximum number of threads here, calls exceeding the limit will be pooled and wait until execution becomes possible.

A function for executing the Worker is returned as the result, which you can then use to call the Worker.

  • src/pages/index.tsx
import { useState } from "react";
import { createWorker } from "worker-lib";
import type { WorkerTest } from "../libs/worker-test";

// Create an instance to execute the Worker
// execute("function name",... parameter) to start the Worker
const execute = createWorker<WorkerTest>(
  () => new Worker(new URL("../libs/worker-test", import.meta.url)),
  5 // Maximum parallel number
);

const Page = () => {
  const [values, setValues] = useState<(number | string)[]>([]);
  const [a, setA] = useState(300);
  const [b, setB] = useState(100);
  return (
    <div>
      <form>
        <input name="a" value={a} onChange={(e) => setA(Number(e.currentTarget.value))} />
        <input name="b" value={b} onChange={(e) => setB(Number(e.currentTarget.value))} />
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("add", a, b);
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          Add
        </button>
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("add2", String(a), String(b));
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          Add(String)
        </button>
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("sub", a, b);
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          Sub
        </button>
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("mul", a, b);
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          Mul
        </button>
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("error", a, b).catch((e) => e);
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          Error
        </button>
      </form>
      {values.map((v, index) => (
        <div key={index}>{v}</div>
      ))}
    </div>
  );
};
export default Page;
const result = await execute("add", a, b);

You use it in the form shown above. Since the type information corresponding to the name is automatically assigned, an error will occur if the parameter types are incorrect. Type information is also automatically set for the return value.

Conclusion

Since creating and using Worker functions has become intuitive, the barrier to entry has likely been lowered significantly. However, the reality is that most processing in web systems can be handled asynchronously, so finding a use case for multi-threading might be the more challenging part.

GitHubで編集を提案

Discussion