-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathindex.ts
More file actions
132 lines (116 loc) · 3.93 KB
/
index.ts
File metadata and controls
132 lines (116 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2026 The Pybricks Authors
/**
* Asserts that an assumption is true. This is used to detect programmer errors
* and should never actually throw in a correctly written program.
* @param condition A condition that is assumed to be true
* @param message Informational message for debugging
*/
export function assert(condition: boolean, message: string): asserts condition {
if (!condition) {
throw Error(message);
}
}
/**
* Asserts that an object is not undefined. This is used to make the type
* checker happy with `maybe()` and saga `race()` and `all()` effects where
* we have the condition "if A is undefined, then B is not undefined".
*/
export function defined<T>(obj: T): asserts obj is NonNullable<T> {
assert(obj !== undefined, 'undefined object');
assert(obj !== null, 'null object');
}
export type Maybe<T> = [T?, Error?];
/** Wraps a promise in try/catch and returns the promise result or error. */
export async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {
try {
return [await promise];
} catch (err) {
return [undefined, ensureError(err)];
}
}
/**
* Formats a number as hex (0x00...)
* @param n The number to format
* @param pad The total number of digits padded with leading 0s
*/
export function hex(n: number, pad: number): string {
return `0x${n.toString(16).padStart(pad, '0')}`;
}
export function isError(err: unknown): err is Error {
const maybeError = err as Error;
return (
maybeError !== undefined &&
typeof maybeError.name === 'string' &&
typeof maybeError.message === 'string'
);
}
export function ensureError(err: unknown): Error {
if (isError(err)) {
return err;
}
if (typeof err === 'string') {
return new Error(err);
}
return Error(String(err));
}
/**
* Gets a timestamp with second resolution suitable for use in a filename.
*/
export function timestamp(): string {
return new Date()
.toISOString()
.replace('T', '_')
.replaceAll(':', '-')
.replace(/\..*$/, '');
}
/**
* Helper function to wrap navigator.locks in a promise so that it can be used
* in code where using it natively doesn't work well (e.g. in sagas). Care must
* be taken so that all code paths (including exceptions) release the lock.
*
* To release the lock, await the returned release function. When the release
* function resolves, the lock will no longer be held.
*
* @param name The name of the lock.
* @param shared If true, the lock will be share (e.g. for reading), otherwise
* the lock will be exclusive (e.g. for writing).
* @returns A release function if the lock was acquired or nothing if the lock
* was already held exclusively by someone else.
*/
export async function acquireLock(
name: string,
shared?: boolean,
): Promise<(() => Promise<void>) | void> {
let lockWaiter: Promise<void>;
const release = await new Promise<(() => void) | void>((resolve, reject) => {
lockWaiter = navigator.locks
.request(
name,
{
ifAvailable: true,
mode: shared ? 'shared' : 'exclusive',
},
(lock) => {
// if the locks is already held, lock will be null here
if (lock === null) {
resolve();
return;
}
// Now we own the lock and it will be held until the returned
// promise is resolved.
return new Promise<void>((resolve2) => resolve(resolve2));
},
)
.catch(reject);
});
if (!release) {
return;
}
return async () => {
// trigger the release
release();
// then wait until the release is complete and the lock is no longer held
await lockWaiter;
};
}