diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..861e3ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + +node_modules/ + +package-lock.json + +.DS_Store +bun.lockb \ No newline at end of file diff --git a/out/@types/batching.d.ts b/out/@types/batching.d.ts new file mode 100644 index 0000000..88b55a8 --- /dev/null +++ b/out/@types/batching.d.ts @@ -0,0 +1,15 @@ +import { ExecutionCallback } from "./typedef"; +/** + * resume 重新开始 + */ +export declare function resumeExecutionCallback(): void; +/** + * pause 暂停执行 + */ +export declare function pauseExecutionCallback(): void; +/** + * false -> 执行中 + * @returns bool + */ +export declare function hasInExecution(): boolean; +export declare function runWithBatchingExecution(callback: ExecutionCallback): void; diff --git a/out/@types/flush.d.ts b/out/@types/flush.d.ts new file mode 100644 index 0000000..7515711 --- /dev/null +++ b/out/@types/flush.d.ts @@ -0,0 +1,2 @@ +export declare function flushCurrentCallback(): void; +export declare function nextLoopFlushCallbackQueue(): void; diff --git a/out/@types/main.d.ts b/out/@types/main.d.ts new file mode 100644 index 0000000..5c1040a --- /dev/null +++ b/out/@types/main.d.ts @@ -0,0 +1,3 @@ +import { ExecutionCallback } from "./typedef"; +export declare function nextTick(fn: ExecutionCallback): void; +export default nextTick; diff --git a/out/@types/nextLoop.d.ts b/out/@types/nextLoop.d.ts new file mode 100644 index 0000000..a2c102e --- /dev/null +++ b/out/@types/nextLoop.d.ts @@ -0,0 +1,2 @@ +export declare function requestNextLoopExecutionCallback(): void; +export declare function nextLoopPauseExecution(): void; diff --git a/out/@types/queue.d.ts b/out/@types/queue.d.ts new file mode 100644 index 0000000..5c318c0 --- /dev/null +++ b/out/@types/queue.d.ts @@ -0,0 +1,6 @@ +import { ExecutionCallback } from "./typedef"; +interface Queue { + nextCallbackQueue: ExecutionCallback[]; +} +export declare const callbackQueue: Queue; +export {}; diff --git a/out/@types/scheduler.d.ts b/out/@types/scheduler.d.ts new file mode 100644 index 0000000..979f525 --- /dev/null +++ b/out/@types/scheduler.d.ts @@ -0,0 +1,2 @@ +import { ExecutionCallback } from "./typedef"; +export declare function schedulerOnCallbackExecution(fn: ExecutionCallback): void; diff --git a/out/@types/slice.d.ts b/out/@types/slice.d.ts new file mode 100644 index 0000000..744eaa2 --- /dev/null +++ b/out/@types/slice.d.ts @@ -0,0 +1,2 @@ +export declare function markStartTime(): void; +export declare function shouldYieldWithStart(): boolean; diff --git a/out/@types/typedef.d.ts b/out/@types/typedef.d.ts new file mode 100644 index 0000000..dd1ade2 --- /dev/null +++ b/out/@types/typedef.d.ts @@ -0,0 +1,3 @@ +export interface ExecutionCallback { + (): void; +} diff --git a/out/batching.js b/out/batching.js new file mode 100644 index 0000000..064ca72 --- /dev/null +++ b/out/batching.js @@ -0,0 +1,31 @@ +let BatchingExecution = true; +/** + * resume 重新开始 + */ +export function resumeExecutionCallback() { + BatchingExecution = true; +} +/** + * pause 暂停执行 + */ +export function pauseExecutionCallback() { + BatchingExecution = false; +} +/** + * false -> 执行中 + * @returns bool + */ +export function hasInExecution() { + return BatchingExecution; +} +export function runWithBatchingExecution(callback) { + try { + const next = hasInExecution(); + if (next) { + return callback(); + } + } + finally { + pauseExecutionCallback(); + } +} diff --git a/out/flush.js b/out/flush.js new file mode 100644 index 0000000..90e57b4 --- /dev/null +++ b/out/flush.js @@ -0,0 +1,50 @@ +import { resumeExecutionCallback } from "./batching"; +import { requestNextLoopExecutionCallback } from "./nextLoop"; +import { callbackQueue } from "./queue"; +import { markStartTime, shouldYieldWithStart } from "./slice"; +let currentCallbackQueue = null; +function requestCurrentQueue() { + if (currentCallbackQueue !== null) { + return currentCallbackQueue; + } + if (callbackQueue.nextCallbackQueue.length > 0) { + currentCallbackQueue = callbackQueue.nextCallbackQueue; + callbackQueue.nextCallbackQueue = []; + return currentCallbackQueue; + } + return null; +} +function shouldNextLoop() { + if (currentCallbackQueue !== null) { + return true; + } + if (callbackQueue.nextCallbackQueue.length > 0) { + return true; + } + return false; +} +export function flushCurrentCallback() { + markStartTime(); + let queue = requestCurrentQueue(); + if (queue === null) { + return; + } + while (queue.length > 0 && !shouldYieldWithStart()) { + let callback = queue.shift(); + callback(); + } + if (queue.length === 0) { + currentCallbackQueue = null; + } +} +export function nextLoopFlushCallbackQueue() { + flushCurrentCallback(); + // 队列不为空, 我希望在下一轮循环中执行他们 + if (shouldNextLoop()) { + requestNextLoopExecutionCallback(); + } + else { + // 本轮循环中立即关闭,否增,下一轮循环关闭时会存在未被执行的回调函数 + resumeExecutionCallback(); + } +} diff --git a/out/main.js b/out/main.js new file mode 100644 index 0000000..137c976 --- /dev/null +++ b/out/main.js @@ -0,0 +1,5 @@ +import { schedulerOnCallbackExecution } from "./scheduler"; +export function nextTick(fn) { + schedulerOnCallbackExecution(fn); +} +export default nextTick; diff --git a/out/nextLoop.js b/out/nextLoop.js new file mode 100644 index 0000000..d92284e --- /dev/null +++ b/out/nextLoop.js @@ -0,0 +1,11 @@ +import { nextLoopFlushCallbackQueue } from "./flush"; +import { pauseExecutionCallback } from "./batching"; +const channel = new MessageChannel(); +channel.port1.onmessage = nextLoopFlushCallbackQueue; +channel.port2.onmessage = pauseExecutionCallback; +export function requestNextLoopExecutionCallback() { + channel.port2.postMessage(null); +} +export function nextLoopPauseExecution() { + channel.port1.postMessage(null); +} diff --git a/out/queue.js b/out/queue.js new file mode 100644 index 0000000..7f9ba68 --- /dev/null +++ b/out/queue.js @@ -0,0 +1,5 @@ +export const callbackQueue = { + nextCallbackQueue: [] +}; +// 使用链表来做 +// 支持ms diff --git a/out/scheduler.js b/out/scheduler.js new file mode 100644 index 0000000..7b34549 --- /dev/null +++ b/out/scheduler.js @@ -0,0 +1,7 @@ +import { runWithBatchingExecution } from "./batching"; +import { requestNextLoopExecutionCallback } from "./nextLoop"; +import { callbackQueue } from "./queue"; +export function schedulerOnCallbackExecution(fn) { + callbackQueue.nextCallbackQueue.push(fn); + runWithBatchingExecution(requestNextLoopExecutionCallback); +} diff --git a/out/slice.js b/out/slice.js new file mode 100644 index 0000000..6f2ee34 --- /dev/null +++ b/out/slice.js @@ -0,0 +1,13 @@ +let _startTime = null; +export function markStartTime() { + _startTime = Date.now(); +} +export function shouldYieldWithStart() { + let end = Date.now(); + let step = end - _startTime; + if (step > 80) { + _startTime = end; + return true; + } + return false; +} diff --git a/out/typedef.js b/out/typedef.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/out/typedef.js @@ -0,0 +1 @@ +export {}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cd73c77 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "next-tick", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f16b48f --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "next-tick", + "version": "0.0.1", + "description": "next event loop, execute callback function before setTimeout", + "types": "./typedef.d.ts", + "main": "out/main.js", + "module": "out/main.js", + "browser": "out/main.js", + + "scripts": { + "tsc":"tsc" + }, + "repository": { + "type": "git", + "url": "https://github.com/lsxqXs/next-tick.git" + }, + "keywords": [ + "promise", + "event loop", + "setTimeout", + "promise", + "async" + ], + "author": "lsxq", + "license": "ISC", + "bugs": { + "url": "https://github.com/lsxqXs/next-tick/issues" + }, + "homepage": "https://github.com/lsxqXs/next-tick#README", + "devDependencies": { + "typescript": "^4.6.4" + }, + "files": [ + "README.md", + "index.d.ts", + "out" + ] +} diff --git a/src/batching.ts b/src/batching.ts new file mode 100644 index 0000000..578e842 --- /dev/null +++ b/src/batching.ts @@ -0,0 +1,40 @@ + +import { ExecutionCallback } from "./typedef"; + +let BatchingExecution = true; + +/** + * resume 重新开始 + */ +export function resumeExecutionCallback() { + BatchingExecution = true; +} + +/** + * pause 暂停执行 + */ +export function pauseExecutionCallback() { + BatchingExecution = false; +} + +/** + * false -> 执行中 + * @returns bool + */ +export function hasInExecution() { + return BatchingExecution; +} + + + +export function runWithBatchingExecution(callback: ExecutionCallback) { + try { + const next = hasInExecution(); + if (next) { + return callback(); + } + } finally { + pauseExecutionCallback(); + } +} + diff --git a/src/flush.ts b/src/flush.ts new file mode 100644 index 0000000..fa50d30 --- /dev/null +++ b/src/flush.ts @@ -0,0 +1,64 @@ +import { resumeExecutionCallback } from "./batching"; +import { requestNextLoopExecutionCallback } from "./nextLoop"; +import { callbackQueue } from "./queue"; +import { markStartTime, shouldYieldWithStart } from "./slice"; +import { ExecutionCallback } from "./typedef"; + + +let currentCallbackQueue: ExecutionCallback[] | null = null; + + +function requestCurrentQueue() { + if (currentCallbackQueue !== null) { + return currentCallbackQueue; + } + if (callbackQueue.nextCallbackQueue.length > 0) { + currentCallbackQueue = callbackQueue.nextCallbackQueue; + callbackQueue.nextCallbackQueue = []; + return currentCallbackQueue; + } + + return null; +} + +function shouldNextLoop() { + if (currentCallbackQueue !== null) { + return true; + } + if (callbackQueue.nextCallbackQueue.length > 0) { + return true; + } + return false; +} + +export function flushCurrentCallback() { + markStartTime(); + let queue = requestCurrentQueue(); + if (queue === null) { + return; + } + + while (queue.length > 0 && !shouldYieldWithStart()) { + let callback = queue.shift(); + callback(); + } + + if (queue.length === 0) { + currentCallbackQueue = null; + } +} + +export function nextLoopFlushCallbackQueue() { + + flushCurrentCallback(); + + // 队列不为空, 我希望在下一轮循环中执行他们 + if (shouldNextLoop()) { + requestNextLoopExecutionCallback(); + } + else { + // 本轮循环中立即关闭,否增,下一轮循环关闭时会存在未被执行的回调函数 + resumeExecutionCallback(); + } + +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..acc732a --- /dev/null +++ b/src/main.ts @@ -0,0 +1,10 @@ + +import {ExecutionCallback} from "./typedef"; +import { schedulerOnCallbackExecution } from "./scheduler"; + + +export function nextTick(fn:ExecutionCallback) { + schedulerOnCallbackExecution(fn); +} + +export default nextTick; diff --git a/src/nextLoop.ts b/src/nextLoop.ts new file mode 100644 index 0000000..b8e2bf9 --- /dev/null +++ b/src/nextLoop.ts @@ -0,0 +1,15 @@ +import { nextLoopFlushCallbackQueue } from "./flush"; +import { pauseExecutionCallback } from "./batching"; + +const channel = new MessageChannel(); + +channel.port1.onmessage = nextLoopFlushCallbackQueue; +channel.port2.onmessage = pauseExecutionCallback; + +export function requestNextLoopExecutionCallback() { + channel.port2.postMessage(null); +} + +export function nextLoopPauseExecution() { + channel.port1.postMessage(null); +} diff --git a/src/queue.ts b/src/queue.ts new file mode 100644 index 0000000..cc86b35 --- /dev/null +++ b/src/queue.ts @@ -0,0 +1,15 @@ + +import {ExecutionCallback} from "./typedef"; + + + +interface Queue { + nextCallbackQueue: ExecutionCallback[]; +} + +export const callbackQueue: Queue = { + nextCallbackQueue: [] +}; + +// 使用链表来做 +// 支持ms diff --git a/src/scheduler.ts b/src/scheduler.ts new file mode 100644 index 0000000..6edcff3 --- /dev/null +++ b/src/scheduler.ts @@ -0,0 +1,10 @@ +import { runWithBatchingExecution } from "./batching"; +import { requestNextLoopExecutionCallback } from "./nextLoop"; +import { callbackQueue } from "./queue"; +import {ExecutionCallback} from "./typedef"; + + +export function schedulerOnCallbackExecution(fn:ExecutionCallback) { + callbackQueue.nextCallbackQueue.push(fn); + runWithBatchingExecution(requestNextLoopExecutionCallback); +} diff --git a/src/slice.ts b/src/slice.ts new file mode 100644 index 0000000..4f0db45 --- /dev/null +++ b/src/slice.ts @@ -0,0 +1,24 @@ + + +let _startTime: number | null = null; + + +export function markStartTime() { + _startTime = Date.now(); +} + + + +export function shouldYieldWithStart() { + let end = Date.now(); + let step = end - _startTime; + + + if (step > 80) { + _startTime = end; + return true; + } + + return false; +} + diff --git a/src/typedef.ts b/src/typedef.ts new file mode 100644 index 0000000..316b598 --- /dev/null +++ b/src/typedef.ts @@ -0,0 +1,6 @@ + + + +export interface ExecutionCallback { + (): void; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..695ecc1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "noEmit": false, + "target": "ESNext", + "declarationDir": "./out/@types", + "outDir": "./out" + }, + "include": [ + "src/*.ts" + ] +} \ No newline at end of file diff --git a/typedef.d.ts b/typedef.d.ts new file mode 100644 index 0000000..be1fca8 --- /dev/null +++ b/typedef.d.ts @@ -0,0 +1,8 @@ + + + + +declare module "next-tick" { + export function nextTick(callback: () => void): void; + export default nextTick; +} \ No newline at end of file