@nxtedition/yield 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.js +29 -9
- package/package.json +11 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) nxtedition
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @nxtedition/yield
|
|
2
|
+
|
|
3
|
+
Cooperative yielding for Node.js to keep servers and applications responsive.
|
|
4
|
+
|
|
5
|
+
When using synchronous APIs like `DatabaseSync`, sync fs API's such as `fs.readFileSync`, or `fs.statSync` — or simply performing lots of synchronous computation — the event loop is blocked and cannot process I/O, timers, or incoming requests. Even long-running chains of asynchronous microtasks (e.g. deeply nested `Promise.then` chains) can starve the event loop in the same way.
|
|
6
|
+
|
|
7
|
+
This library provides a simple mechanism to periodically yield control back to the event loop during these long-running operations, preventing event loop starvation and keeping your application responsive.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @nxtedition/yield
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Promise-based
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { maybeYield } from '@nxtedition/yield'
|
|
21
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
22
|
+
|
|
23
|
+
const db = new DatabaseSync('data.db')
|
|
24
|
+
const rows = db.prepare('SELECT * FROM large_table').all()
|
|
25
|
+
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
processRow(row)
|
|
28
|
+
const yielded = maybeYield()
|
|
29
|
+
if (yielded) {
|
|
30
|
+
await yielded // give the event loop a turn
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Callback-based
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import { maybeYield } from '@nxtedition/yield'
|
|
39
|
+
import fs from 'node:fs'
|
|
40
|
+
|
|
41
|
+
function processFiles(files, i = 0) {
|
|
42
|
+
for (; i < files.length; i++) {
|
|
43
|
+
const data = fs.readFileSync(files[i])
|
|
44
|
+
transform(data)
|
|
45
|
+
if (maybeYield(processFiles, files, i + 1)) {
|
|
46
|
+
return // will resume via callback after yielding
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Unconditional yield
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { doYield } from '@nxtedition/yield'
|
|
56
|
+
|
|
57
|
+
// Promise-based
|
|
58
|
+
await doYield()
|
|
59
|
+
|
|
60
|
+
// Callback-based
|
|
61
|
+
doYield((opaque) => {
|
|
62
|
+
continueWork(opaque)
|
|
63
|
+
}, data)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Checking without yielding
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { shouldYield } from '@nxtedition/yield'
|
|
70
|
+
|
|
71
|
+
while (hasWork()) {
|
|
72
|
+
doWork()
|
|
73
|
+
if (shouldYield()) {
|
|
74
|
+
scheduleMoreWork()
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
### `shouldYield(timeout?): boolean`
|
|
83
|
+
|
|
84
|
+
Returns `true` when the current synchronous execution has exceeded the timeout threshold (default: 40ms). Does not yield — only checks whether yielding is recommended.
|
|
85
|
+
|
|
86
|
+
### `maybeYield(timeout?): Promise<void> | null`
|
|
87
|
+
|
|
88
|
+
If a yield is needed, queues a yield and returns a `Promise`. Otherwise returns `null`. Use this in `async` functions.
|
|
89
|
+
|
|
90
|
+
### `maybeYield(callback, opaque?, timeout?): void`
|
|
91
|
+
|
|
92
|
+
If a yield is needed, defers `callback(opaque)` to after the yield. Otherwise calls `callback(opaque)` synchronously.
|
|
93
|
+
|
|
94
|
+
### `doYield(): Promise<void>`
|
|
95
|
+
|
|
96
|
+
Unconditionally queues a yield and returns a `Promise` that resolves after the event loop has been given a turn.
|
|
97
|
+
|
|
98
|
+
### `doYield(callback, opaque?): void`
|
|
99
|
+
|
|
100
|
+
Unconditionally defers `callback(opaque)` to after the next yield.
|
|
101
|
+
|
|
102
|
+
### `setYieldTimeout(timeout): void`
|
|
103
|
+
|
|
104
|
+
Set the default yield timeout in milliseconds. Must be a non-negative number. Default: `40`.
|
|
105
|
+
|
|
106
|
+
### `setYieldDispatcher(dispatcher): void`
|
|
107
|
+
|
|
108
|
+
Override the scheduling function used to defer work. The dispatcher receives a callback and should invoke it asynchronously (e.g. via `setTimeout` or `setImmediate`). Pass `null` to restore the default dispatcher.
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
The library tracks when the last yield occurred using `performance.now()`. When `shouldYield()` detects that more than `timeout` milliseconds have elapsed, it signals that a yield is due. `doYield` and `maybeYield` batch pending callbacks into a queue that is drained in a single microtask turn, yielding again if the timeout is exceeded during draining.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
package/lib/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ type Dispatcher = (callback: () => void) => void;
|
|
|
2
2
|
export declare function setYieldDispatcher(schedule: Dispatcher | null): void;
|
|
3
3
|
export declare function setYieldTimeout(timeout: number): void;
|
|
4
4
|
export declare function shouldYield(timeout?: number): boolean;
|
|
5
|
-
export declare function maybeYield<T>(callback
|
|
6
|
-
export declare function
|
|
5
|
+
export declare function maybeYield<T>(callback: (opaque: T | undefined) => void, opaque?: T, timeout?: number): void;
|
|
6
|
+
export declare function maybeYield(timeout?: number): Promise<void> | null;
|
|
7
|
+
export declare function doYield<T>(callback: (opaque: T | undefined) => void, opaque?: T): void;
|
|
8
|
+
export declare function doYield(): Promise<void>;
|
|
7
9
|
export {};
|
package/lib/index.js
CHANGED
|
@@ -34,26 +34,47 @@ export function shouldYield(timeout ) {
|
|
|
34
34
|
return yieldActive
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
37
43
|
export function maybeYield (
|
|
38
|
-
|
|
44
|
+
callbackOrTimeout ,
|
|
39
45
|
opaque ,
|
|
40
46
|
timeout ,
|
|
41
|
-
)
|
|
47
|
+
) {
|
|
48
|
+
const callback = typeof callbackOrTimeout === 'function' ? callbackOrTimeout : undefined
|
|
49
|
+
if (timeout === undefined && typeof callbackOrTimeout === 'number') {
|
|
50
|
+
timeout = callbackOrTimeout
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
if (callback != null && typeof callback !== 'function') {
|
|
43
54
|
throw new TypeError('callback must be a function')
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
if (shouldYield(timeout)) {
|
|
47
|
-
|
|
58
|
+
if (callback != null) {
|
|
59
|
+
doYield(callback, opaque)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
return doYield()
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
if (callback != null) {
|
|
51
66
|
callback(opaque)
|
|
67
|
+
return
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
return null
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
57
78
|
export function doYield (
|
|
58
79
|
callback ,
|
|
59
80
|
opaque ,
|
|
@@ -67,14 +88,13 @@ export function doYield (
|
|
|
67
88
|
yieldSchedule(dispatchYield)
|
|
68
89
|
}
|
|
69
90
|
|
|
70
|
-
if (callback
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
if (callback == null) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
yieldQueue.push(resolve, null)
|
|
94
|
+
})
|
|
73
95
|
}
|
|
74
96
|
|
|
75
|
-
|
|
76
|
-
yieldQueue.push(resolve, null)
|
|
77
|
-
})
|
|
97
|
+
yieldQueue.push(callback, opaque)
|
|
78
98
|
}
|
|
79
99
|
|
|
80
100
|
function dispatchYield() {
|
package/package.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/yield",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"lib"
|
|
8
|
+
"lib",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
9
11
|
],
|
|
10
|
-
"license": "
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
11
16
|
"scripts": {
|
|
12
17
|
"build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/",
|
|
13
18
|
"prepublishOnly": "yarn build",
|
|
@@ -16,9 +21,10 @@
|
|
|
16
21
|
"test:ci": "node --test"
|
|
17
22
|
},
|
|
18
23
|
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.2.3",
|
|
19
25
|
"amaroc": "^1.0.1",
|
|
26
|
+
"oxlint-tsgolint": "^0.12.2",
|
|
20
27
|
"rimraf": "^6.1.2",
|
|
21
28
|
"typescript": "^5.9.3"
|
|
22
|
-
}
|
|
23
|
-
"gitHead": "dc18f1fb0cf53929205b2de3cb53c08b631a4a9d"
|
|
29
|
+
}
|
|
24
30
|
}
|