feat: ticking promise

This commit is contained in:
cha0s 2024-06-22 05:05:53 -05:00
parent 1a908bc0af
commit 9e5efdf413
2 changed files with 153 additions and 0 deletions

View File

@ -0,0 +1,53 @@
export default class TickingPromise extends Promise {
constructor(executor, ticker) {
let _reject;
let _resolve;
super((resolve, reject) => {
_reject = reject;
_resolve = resolve;
if (executor) {
executor(resolve, reject);
}
});
this.reject = _reject;
this.resolve = _resolve;
this.ticker = ticker;
}
static all(promises) {
const tickingPromises = [];
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
if (promise instanceof TickingPromise) {
tickingPromises.push(promise);
// After resolution, stop ticking the promise.
promise.then(() => {
tickingPromises.splice(tickingPromises.indexOf(promise), 1);
});
}
}
/* v8 ignore next 3 */
if (0 === tickingPromises.length) {
return super.all(promises);
}
return new TickingPromise(
(resolve, reject) => {
super.all(promises)
.then(resolve)
/* v8 ignore next */
.catch(reject);
},
(elapsed) => {
for (let i = 0; i < tickingPromises.length; i++) {
tickingPromises[i].tick(elapsed);
}
},
);
}
tick(elapsed) {
this.ticker(elapsed, this.resolve, this.reject);
}
}

View File

@ -0,0 +1,100 @@
import {expect, test} from 'vitest';
import TickingPromise from './ticking-promise.js';
test('runs executor', async () => {
expect(
await new TickingPromise((resolve, reject) => {
resolve(32);
}),
)
.to.equal(32);
expect(
async () => {
await new TickingPromise((resolve, reject) => {
reject(new Error(''));
})
}
)
.rejects.toThrowError('');
});
test('ticks and resolves', async () => {
let done = false;
let e = 0;
const tp = new TickingPromise(undefined, (elapsed, resolve) => {
e += elapsed;
if (1 === e) {
done = true;
resolve(16);
}
});
expect(done)
.to.be.false;
tp.tick(0.25);
expect(done)
.to.be.false;
tp.tick(0.25);
expect(done)
.to.be.false;
tp.tick(0.25);
expect(done)
.to.be.false;
tp.tick(0.25);
expect(done)
.to.be.true;
expect(await tp)
.to.equal(16);
});
test('ticks and rejects', async () => {
let caught = false;
const tp = new TickingPromise(undefined, (elapsed, resolve, reject) => {
reject(new Error());
});
tp.catch(() => {
caught = true;
});
expect(caught)
.to.be.false;
tp.tick(0.25);
await Promise.resolve();
expect(caught)
.to.be.true;
});
test('handles all', async () => {
let done = 0;
let e1 = 0, e2 = 0;
const tp1 = new TickingPromise(undefined, (elapsed, resolve) => {
e1 += elapsed;
if (1 === e1) {
done += 1;
resolve(16);
}
});
const tp2 = new TickingPromise(undefined, (elapsed, resolve) => {
e2 += elapsed;
if (2 === e2) {
done += 1;
resolve(32);
}
});
const tpa = TickingPromise.all([
Promise.resolve(8),
tp1,
tp2,
]);
expect(done)
.to.equal(0);
while (2 !== done) {
tpa.tick(0.25);
await Promise.resolve();
}
expect(e1)
.to.equal(1);
expect(e2)
.to.equal(2);
expect(await tpa)
.to.deep.equal([8, 16, 32]);
});