Skip to content

Commit f183241

Browse files
feat: Migrate to typescript (#40)
BREAKING CHANGE: Minimum NodeJS version set to `^20.19.0 || ^22.12.0 || >=24.0.0`
1 parent 8669ad9 commit f183241

File tree

5 files changed

+110
-117
lines changed

5 files changed

+110
-117
lines changed

.mocharc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module.exports = {
2-
require: ['ts-node/register'],
2+
require: ['tsx/cjs'],
33
forbidOnly: Boolean(process.env.CI)
44
};
Lines changed: 60 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import B from 'bluebird';
22
import _ from 'lodash';
3+
import type {LongSleepOptions, WaitForConditionOptions} from './types.js';
34

45
const LONG_SLEEP_THRESHOLD = 5000; // anything over 5000ms will turn into a spin
56

67
/**
78
* An async/await version of setTimeout
8-
* @param {number} ms
9-
* @returns {Promise<void>}
109
*/
11-
async function sleep (ms) {
10+
export async function sleep(ms: number): Promise<void> {
1211
return await B.delay(ms);
1312
}
1413

@@ -17,24 +16,20 @@ async function sleep (ms) {
1716
* times. To safely wait for these long times (e.g. in the 5+ minute range), you
1817
* can use `longSleep`.
1918
*
20-
* sYou can also pass a `progressCb` option which is a callback function that
19+
* You can also pass a `progressCb` option which is a callback function that
2120
* receives an object with the properties `elapsedMs`, `timeLeft`, and
2221
* `progress`. This will be called on every wait interval so you can do your
2322
* wait logging or whatever.
24-
* @param {number} ms
25-
* @param {LongSleepOptions} [opts]
26-
* @returns {Promise<void>}
2723
*/
28-
async function longSleep (ms, {
29-
thresholdMs = LONG_SLEEP_THRESHOLD,
30-
intervalMs = 1000,
31-
progressCb = null,
32-
} = {}) {
24+
export async function longSleep(
25+
ms: number,
26+
{thresholdMs = LONG_SLEEP_THRESHOLD, intervalMs = 1000, progressCb = null}: LongSleepOptions = {},
27+
): Promise<void> {
3328
if (ms < thresholdMs) {
3429
return await sleep(ms);
3530
}
3631
const endAt = Date.now() + ms;
37-
let timeLeft;
32+
let timeLeft: number;
3833
let elapsedMs = 0;
3934
do {
4035
const pre = Date.now();
@@ -50,16 +45,15 @@ async function longSleep (ms, {
5045

5146
/**
5247
* An async/await way of running a method until it doesn't throw an error
53-
* @template [T=any]
54-
* @param {number} times
55-
* @param {(...args: any[]) => Promise<T>} fn
56-
* @param {...any} args
57-
* @returns {Promise<T?>}
5848
*/
59-
async function retry (times, fn, ...args) {
49+
export async function retry<T = any>(
50+
times: number,
51+
fn: (...args: any[]) => Promise<T>,
52+
...args: any[]
53+
): Promise<T | null> {
6054
let tries = 0;
6155
let done = false;
62-
let res = null;
56+
let res: T | null = null;
6357
while (!done && tries < times) {
6458
tries++;
6559
try {
@@ -77,18 +71,17 @@ async function retry (times, fn, ...args) {
7771
/**
7872
* You can also use `retryInterval` to add a sleep in between retries. This can
7973
* be useful if you want to throttle how fast we retry.
80-
* @template [T=any]
81-
* @param {number} times
82-
* @param {number} sleepMs
83-
* @param {(...args: any[]) => Promise<T>} fn
84-
* @param {...any} args
85-
* @returns {Promise<T?>}
8674
*/
87-
async function retryInterval (times, sleepMs, fn, ...args) {
75+
export async function retryInterval<T = any>(
76+
times: number,
77+
sleepMs: number,
78+
fn: (...args: any[]) => Promise<T>,
79+
...args: any[]
80+
): Promise<T | null> {
8881
let count = 0;
89-
let wrapped = async () => {
82+
const wrapped = async (): Promise<T> => {
9083
count++;
91-
let res;
84+
let res: T;
9285
try {
9386
res = await fn(...args);
9487
} catch (e) {
@@ -103,82 +96,78 @@ async function retryInterval (times, sleepMs, fn, ...args) {
10396
return await retry(times, wrapped);
10497
}
10598

106-
const parallel = B.all;
99+
export const parallel = B.all;
107100

108101
/**
109102
* Export async functions (Promises) and import this with your ES5 code to use
110103
* it with Node.
111-
* @template [R=any]
112-
* @param {any} promisey
113-
* @param {(err: any, value?: R) => void} cb
114-
* @returns {Promise<R>}
115104
*/
116-
function nodeify (promisey, cb) { // eslint-disable-line promise/prefer-await-to-callbacks
105+
// eslint-disable-next-line promise/prefer-await-to-callbacks
106+
export function nodeify<R = any>(promisey: any, cb: (err: any, value?: R) => void): Promise<R> {
117107
return B.resolve(promisey).nodeify(cb);
118108
}
119109

120110
/**
121111
* Node-ify an entire object of `Promise`-returning functions
122-
* @param {Record<string,(...args: any[]) => any>} promiseyMap
123-
* @returns {Record<string,(...args: any[])=>void>}
124112
*/
125-
function nodeifyAll (promiseyMap) {
126-
/** @type {Record<string,(...args: any[])=>void>} */
127-
let cbMap = {};
113+
export function nodeifyAll<T extends Record<string, (...args: any[]) => any>>(
114+
promiseyMap: T,
115+
): Record<string, (...args: any[]) => void> {
116+
const cbMap: Record<string, (...args: any[]) => void> = {};
128117
for (const [name, fn] of _.toPairs(promiseyMap)) {
129-
cbMap[name] = function (...args) {
130-
const _cb = args.slice(-1)[0];
131-
args = args.slice(0, -1);
132-
nodeify(fn(...args), _cb);
118+
cbMap[name] = function (...args: any[]) {
119+
const _cb = args.slice(-1)[0] as (err: any, ...values: any[]) => void;
120+
const fnArgs = args.slice(0, -1);
121+
nodeify(fn(...fnArgs), _cb);
133122
};
134123
}
135124
return cbMap;
136125
}
137126

138127
/**
139-
* @param {(...args: any[]) => any|Promise<any>} fn
140-
* @param {...any} args
128+
* Fire and forget async function execution
141129
*/
142-
function asyncify (fn, ...args) {
130+
export function asyncify(fn: (...args: any[]) => any | Promise<any>, ...args: any[]): void {
143131
B.resolve(fn(...args)).done();
144132
}
145133

146134
/**
147-
* Similar to `Array.prototype.map`; runs in serial
148-
* @param {any[]} coll
149-
* @param {(value: any) => any|Promise<any>} mapper
150-
* @returns {Promise<any[]>}
135+
* Similar to `Array.prototype.map`; runs in serial or parallel
151136
*/
152-
async function asyncmap (coll, mapper, runInParallel = true) {
137+
export async function asyncmap<T, R>(
138+
coll: T[],
139+
mapper: (value: T) => R | Promise<R>,
140+
runInParallel = true,
141+
): Promise<R[]> {
153142
if (runInParallel) {
154143
return parallel(coll.map(mapper));
155144
}
156145

157-
let newColl = [];
158-
for (let item of coll) {
146+
const newColl: R[] = [];
147+
for (const item of coll) {
159148
newColl.push(await mapper(item));
160149
}
161150
return newColl;
162151
}
163152

164153
/**
165154
* Similar to `Array.prototype.filter`
166-
* @param {any[]} coll
167-
* @param {(value: any) => any|Promise<any>} filter
168-
* @param {boolean} runInParallel
169-
* @returns {Promise<any[]>}
170155
*/
171-
async function asyncfilter (coll, filter, runInParallel = true) {
172-
let newColl = [];
156+
export async function asyncfilter<T>(
157+
coll: T[],
158+
filter: (value: T) => boolean | Promise<boolean>,
159+
runInParallel = true,
160+
): Promise<T[]> {
161+
const newColl: T[] = [];
173162
if (runInParallel) {
174-
let bools = await parallel(coll.map(filter));
163+
const bools = await parallel(coll.map(filter));
175164
for (let i = 0; i < coll.length; i++) {
176165
if (bools[i]) {
177166
newColl.push(coll[i]);
178167
}
179168
}
180169
} else {
181-
for (let item of coll) {
170+
for (const item of coll) {
182171
if (await filter(item)) {
183172
newColl.push(item);
184173
}
@@ -199,23 +188,20 @@ async function asyncfilter (coll, filter, runInParallel = true) {
199188
* error then this exception will be immediately passed through.
200189
*
201190
* The default options are: `{ waitMs: 5000, intervalMs: 500 }`
202-
* @template T
203-
* @param {() => Promise<T>|T} condFn
204-
* @param {WaitForConditionOptions} [options]
205-
* @returns {Promise<T>}
206191
*/
207-
async function waitForCondition (condFn, options = {}) {
208-
/** @type {WaitForConditionOptions & {waitMs: number, intervalMs: number}} */
209-
const opts = _.defaults(options, {
192+
export async function waitForCondition<T>(
193+
condFn: () => Promise<T> | T,
194+
options: WaitForConditionOptions = {},
195+
): Promise<T> {
196+
const opts: WaitForConditionOptions & {waitMs: number; intervalMs: number} = _.defaults(options, {
210197
waitMs: 5000,
211198
intervalMs: 500,
212199
});
213200
const debug = opts.logger ? opts.logger.debug.bind(opts.logger) : _.noop;
214201
const error = opts.error;
215202
const begunAt = Date.now();
216203
const endAt = begunAt + opts.waitMs;
217-
/** @returns {Promise<T>} */
218-
const spin = async function spin () {
204+
const spin = async function spin(): Promise<T> {
219205
const result = await condFn();
220206
if (result) {
221207
return result;
@@ -230,45 +216,10 @@ async function waitForCondition (condFn, options = {}) {
230216
}
231217
// if there is an error option, it is either a string message or an error itself
232218
throw error
233-
? (_.isString(error) ? new Error(error) : error)
219+
? _.isString(error)
220+
? new Error(error)
221+
: error
234222
: new Error(`Condition unmet after ${waited} ms. Timing out.`);
235223
};
236224
return await spin();
237225
}
238-
239-
export {
240-
sleep, retry, nodeify, nodeifyAll, retryInterval, asyncify, parallel,
241-
asyncmap, asyncfilter, waitForCondition, longSleep,
242-
};
243-
244-
/**
245-
* Options for {@link waitForCondition}
246-
* @typedef WaitForConditionOptions
247-
* @property {number} [waitMs]
248-
* @property {number} [intervalMs]
249-
* @property {{debug: (...args: any[]) => void}} [logger]
250-
* @property {string|Error} [error]
251-
*/
252-
253-
/**
254-
* Options for {@link longSleep}
255-
* @typedef LongSleepOptions
256-
* @property {number} [thresholdMs]
257-
* @property {number} [intervalMs]
258-
* @property {ProgressCallback?} [progressCb]
259-
*/
260-
261-
/**
262-
* Parameter provided to a {@link ProgressCallback}
263-
* @typedef Progress
264-
* @property {number} elapsedMs
265-
* @property {number} timeLeft
266-
* @property {number} progress
267-
*/
268-
269-
/**
270-
* Progress callback for {@link longSleep}
271-
* @callback ProgressCallback
272-
* @param {Progress} progress
273-
* @returns {void}
274-
*/

lib/types.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Parameter provided to a progress callback
3+
*/
4+
export interface Progress {
5+
elapsedMs: number;
6+
timeLeft: number;
7+
progress: number;
8+
}
9+
10+
/**
11+
* Progress callback for {@link longSleep}
12+
*/
13+
export type ProgressCallback = (progress: Progress) => void;
14+
15+
/**
16+
* Options for {@link longSleep}
17+
*/
18+
export interface LongSleepOptions {
19+
thresholdMs?: number;
20+
intervalMs?: number;
21+
progressCb?: ProgressCallback | null;
22+
}
23+
24+
/**
25+
* Options for {@link waitForCondition}
26+
*/
27+
export interface WaitForConditionOptions {
28+
waitMs?: number;
29+
intervalMs?: number;
30+
logger?: {
31+
debug: (...args: any[]) => void;
32+
};
33+
error?: string | Error;
34+
}

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
"url": "https://github.com/jlipps/asyncbox/issues"
1818
},
1919
"engines": {
20-
"node": ">=16"
20+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
21+
"npm": ">=10"
2122
},
2223
"main": "./build/lib/asyncbox.js",
2324
"bin": {},
2425
"directories": {
2526
"lib": "./lib"
2627
},
2728
"files": [
28-
"lib/**/*.js",
29+
"lib/**/*",
2930
"build/lib/**/*"
3031
],
3132
"dependencies": {
@@ -41,27 +42,34 @@
4142
"prepare": "npm run rebuild",
4243
"test": "mocha --exit --timeout 1m \"./test/**/*-specs.ts\"",
4344
"lint": "eslint .",
45+
"format": "prettier -w ./lib",
4446
"watch": "npm run dev"
4547
},
48+
"prettier": {
49+
"bracketSpacing": false,
50+
"printWidth": 100,
51+
"singleQuote": true
52+
},
4653
"devDependencies": {
4754
"@appium/eslint-config-appium-ts": "^2.0.5",
4855
"@appium/tsconfig": "^1.0.0",
4956
"@semantic-release/changelog": "^6.0.1",
5057
"@semantic-release/git": "^10.0.1",
5158
"@types/bluebird": "^3.5.37",
52-
"@types/chai": "^5.2.3",
5359
"@types/lodash": "^4.14.189",
5460
"@types/mocha": "^10.0.10",
5561
"@types/node": "^24.10.1",
5662
"chai": "^6.2.1",
5763
"chai-as-promised": "^8.0.2",
5864
"conventional-changelog-conventionalcommits": "^9.0.0",
5965
"eslint": "^9.39.1",
66+
"prettier": "^3.0.0",
6067
"mocha": "^11.7.5",
6168
"semantic-release": "^25.0.2",
6269
"sinon": "^21.0.0",
6370
"ts-node": "^10.9.1",
71+
"tsx": "^4.21.0",
6472
"typescript": "^5.1.6"
6573
},
66-
"types": "./build/lib/asyncbox.d.ts"
74+
"types": "./build/lib/types.d.ts"
6775
}

0 commit comments

Comments
 (0)