run-time-opti:一个好用的优化js运行时的库

702 阅读4分钟

核心思路:

基于rail标准,通过webworker,长任务拆分,分帧执行,让出主线程,利用浏览器空闲执行等方式
避免js线程长期执行,阻塞UI渲染。以达到性能优化的目的。

截屏2024-03-03 下午5.29.57.png

项目源码&示例&视频

github: run-time-opti

new-thread.png

normal 无优化的效果 运行长任务

normal.png

优化后分帧运行 frame

frame.png

    npm install run-time-opti || yarn add run-time-opti

默认ES6语法 使用方自行转译和polyfill


    const p1 = (res) => {
      return 1111;
    };

    const p2 = (res) => {
      return 2222;
    };

    const p3 = (res) =>
      new Promise((resolve) => {
        setTimeout(() => {
          resolve(33333);
        }, 2000);
      });
    const p4 = (res) =>
      new Promise((resolve, reject) => {
        setTimeout(() => {
          reject(4444);
        }, 6000);
      });
    const p5 = (res) =>
      new Promise((resolve) => {
        setTimeout(() => {
          resolve(5555);
        }, 1000);
      });

功能:

yieldToMain

让出主线程 避免长任务阻塞UI 造成页面卡顿
此方法后面的任务将在下一帧继续执行。
优先使用MessageChannel宏任务&无延迟 使用setTimeOut兼容处理

    import {yieldToMain} from "run-time-opti";
    p1()
    await yieldToMain(); // 中断当前帧 让出给main thread 下一帧继续执行
    p2();

sleep

    import { sleep } from "run-time-opti";
    console.log('111111')
    await sleep(2000) // 两秒后继续执行
    console.log('222222')

nextFrameExecute

下一帧执行某个任务
优先使用requestAnimationFrame 使用setTimeout做兼容。

    import { nextFrameExecute } from "run-time-opti";
    p1()
    nextFrameExecute(p2) // 将在下一帧执行这个任务
    p3()

DynamicTasks

有UI操作并且优先级较高 建议使用DynamicTasks的方式 避免卡顿使用frame参数分帧运行

  • 支持动态添加
  • 支持并行&串行
  • 支持同步&异步
  • 支持时间切片
    import {DynamicTasks} from "run-time-opti";
    const task = new DynamicTasks({ parallelMax: 3, frame: true });
    task.start([
      {
        key: "p1",
        task: p1,
        parallel: true,
      },
      {
        key: "p2",
        task: p2,
        parallel: true,
      },
    ]);

    task.start([
      {
        key: "p3",
        task: p3,
        parallel: true,
      },
      {
        key: "p4",
        task: p4,
        parallel: true,
      },
      {
        key: "p5",
        task: p5, // 默认的串行的 会等待前面的全部执行完成 并且可以获取前面的结果
      },
      {
        key: "p6",
        task: (allResult)=>{
            console.log('test allResult', allResult)
        },
      },
    ]);

pool 支持web worker线程池

无UI操作 大量运算 建议使用pool的线程池方式运行。
run in web worker thread pool。
独立main thread上下文 使用new Function转换运行,因此不能访问外部变量。
可以通过串行的方式(默认就是串行),获取到上一个task的结果。
可以通过网络获取数据运算。

    import { pool } from "run-time-opti"
    const p1 = (res) => {
      console.log("test p1 res", res, 1111);
      return 1111;
    };
    const p2 = (res) => {
      console.log("test p2 res", 22222);
    };
    const p3 = (res) =>
      new Promise((resolve) => {
        const test = () => {
          let count = 0;
          for (let i = 0; i < 1000000000; i++) {
            count = count + i;
          }
        };
        test();
        setTimeout(() => {
          resolve(33333);
        }, 5000);
        console.log("test p3 res", res, 3333333);
      });

    pool([
      {
        key: "p1",
        task: p1,
      },
      {
        key: "p2",
        task: p2,
      },
      {
        key: "p3",
        task: p3,
      },
    ], 2).then((res) =>{
      console.log("test pool:", res);
      {
        p1: {
          data: 1111,
          key: "p1",
          status: "succ",
        },
        p2: {
          data: undefined,
          key: "p2",
          status: "succ",
        },
        p3: {
          data: 33333,
          key: "p3",
          status: "succ",
        }
      }
    });

clearPool

    import { clearPool } from "run-time-opti"
    clearPool()

idleCallback

浏览器空闲执行 不紧急的任务建议使用这个api
不建议UI操作:
原因1:此时已经渲染完成,UI变更会导致页面重绘应尽量避免
原因2:因为调用时机不确定 dom操作会导致页面视觉变动难以预测
参考react fiber思路通过raf+messagechannel 对不支持requestidlecallback的浏览器做了polyfill。

    import { idleCallback } from "run-time-opti"
    idleCallback((params)=>{
      console.log('test idleCallback params', params)
      }, { timeout: 100})

idle

浏览器空闲执行 不紧急的任务建议使用这个api
此时已经渲染完成,UI变更会导致页面重绘应尽量避免
内部使用idleCallback方法。

    import { idle } from "run-time-opti"
     idle([{key: 'p1',task: p1}],100).then(res => {
      console.log('test idle:', res)
    })

serialTask

顺序执行一系列任务 并返回结果

    import {serialTask} from "run-time-opti";
    serialTask([p1,p2,p3]).then(res=>{
        console.log("test res", res)
      })
      res:
      [
      { status: 'succ', data: 1111 },
      { status: 'succ', data: 2222 },
      { status: 'succ', data: 33333 }
    ]

parallelMaxTask

并发执行一系列任务并返回结果

    import {parallelMaxTask} from "run-time-opti";
      parallelMaxTask([p1,p2, p3], 2).then((res)=>{
        console.log('test parallelMaxTask: ', res)
        })
      res:
      [
      { status: 'succ', data: 1111 },
      { status: 'succ', data: 2222 },
      { status: 'succ', data: 33333 }
    ]

TaskCancelable

封装的一个可以取消的promise任务

    import {TaskCancelable} from "run-time-opti
    const p3 = () =>
      new Promise((resolve) => {
        setTimeout(() => {
          resolve(33333);
        }, 2000);
      });
    const cancelP = TaskCancelable(p3());
    cancelP
      .then((res) => {
        console.log("test res", res);
      })
      .catch(() => {
        if (cancelP.isCancel()) { // true
          console.log("test cancel");
        }
      })
      .finally(() => {
        console.log("test finally isCancel:", cancelP.isCancel()); // true
      });
    cancelP.cancel();

KillAwait

Promise的同步调用 消除异步的传染性

import { KillAwait } from "run-time-opti"
const test3 = () => {
  const res = KillAwait.promise((resolve) => {
    setTimeout(() => {
      resolve(3);
    }, 2000);
  });
  console.log("test 3 res", res);
  return res;
};

const test2 = () => {
  const res = KillAwait.promise((resolve) => {
    setTimeout(() => {
      resolve(2);
    }, 2000);
  });

  console.log("test 2 res", res);
  return test3();
};

const test1 = () => {
  const res1 = KillAwait.promise((resolve) => {
    setTimeout(() => {
      resolve(1);
    }, 2000);
  });

  console.log("test 1 res1", res1);
  const res2 = KillAwait.promise((resolve) => {
    setTimeout(() => {
      resolve(1.2);
    }, 2000);
  });

  console.log("test 1 res2", res2);
  return test2();
};

const main2 = () => {
  const res = test1();
  console.log("test 最终结果 res", res);
};
KillAwait.execute(main2);
// res
test 1 res1 1
test 1 res1 1
test 1 res2 1.2
test 1 res1 1
test 1 res2 1.2
test 2 res 2
test 1 res1 1
test 1 res2 1.2
test 2 res 2
test 3 res 3
test 最终结果 res 3

microTask & macroTask

import { microTask, macroTask } from "run-time-opti"
microTask(()=>{
  console.log('microTask')
})

macroTask(()=>{
  console.log('macroTask')
})