@modern-js/runtime-utils
Version:
A Progressive React Framework for modern web development.
120 lines (119 loc) • 4.55 kB
JavaScript
import "node:module";
function invariant(value, message) {
if (false === value || null == value) throw new Error(message);
}
class AbortedDeferredError extends Error {
}
function isTrackedPromise(value) {
return value instanceof Promise && true === value._tracked;
}
function unwrapTrackedPromise(value) {
if (!isTrackedPromise(value)) return value;
if (value._error) throw value._error;
return value._data;
}
class DeferredData {
trackPromise(key, value) {
if (!(value instanceof Promise)) return value;
this.deferredKeys.push(key);
this.pendingKeysSet.add(key);
const promise = Promise.race([
value,
this.abortPromise
]).then((data)=>this.onSettle(promise, key, void 0, data), (error)=>this.onSettle(promise, key, error));
promise.catch(()=>{});
Object.defineProperty(promise, '_tracked', {
get: ()=>true
});
return promise;
}
onSettle(promise, key, error, data) {
if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
this.unlistenAbortSignal();
Object.defineProperty(promise, '_error', {
get: ()=>error
});
return Promise.reject(error);
}
this.pendingKeysSet.delete(key);
if (this.done) this.unlistenAbortSignal();
if (void 0 === error && void 0 === data) {
const undefinedError = new Error(`Deferred data for key "${key}" resolved/rejected with \`undefined\`, you must resolve/reject with a value or \`null\`.`);
Object.defineProperty(promise, '_error', {
get: ()=>undefinedError
});
this.emit(false, key);
return Promise.reject(undefinedError);
}
if (void 0 === data) {
Object.defineProperty(promise, '_error', {
get: ()=>error
});
this.emit(false, key);
return Promise.reject(error);
}
Object.defineProperty(promise, '_data', {
get: ()=>data
});
this.emit(false, key);
return data;
}
emit(aborted, settledKey) {
this.subscribers.forEach((subscriber)=>subscriber(aborted, settledKey));
}
subscribe(fn) {
this.subscribers.add(fn);
return ()=>this.subscribers.delete(fn);
}
cancel() {
this.controller.abort();
this.pendingKeysSet.forEach((v, k)=>this.pendingKeysSet.delete(k));
this.emit(true);
}
async resolveData(signal) {
let aborted = false;
if (!this.done) {
const onAbort = ()=>this.cancel();
signal.addEventListener('abort', onAbort);
aborted = await new Promise((resolve)=>{
this.subscribe((aborted)=>{
signal.removeEventListener('abort', onAbort);
if (aborted || this.done) resolve(aborted);
});
});
}
return aborted;
}
get done() {
return 0 === this.pendingKeysSet.size;
}
get unwrappedData() {
invariant(null !== this.data && this.done, 'Can only unwrap data on initialized and settled deferreds');
return Object.entries(this.data).reduce((acc, [key, value])=>Object.assign(acc, {
[key]: unwrapTrackedPromise(value)
}), {});
}
get pendingKeys() {
return Array.from(this.pendingKeysSet);
}
constructor(data, responseInit){
this.pendingKeysSet = new Set();
this.subscribers = new Set();
this.__modern_deferred = true;
this.deferredKeys = [];
invariant(data && 'object' == typeof data && !Array.isArray(data), 'defer() only accepts plain objects');
let reject;
this.abortPromise = new Promise((_, r)=>reject = r);
this.controller = new AbortController();
const onAbort = ()=>reject(new AbortedDeferredError('Deferred data aborted'));
this.unlistenAbortSignal = ()=>this.controller.signal.removeEventListener('abort', onAbort);
this.controller.signal.addEventListener('abort', onAbort);
this.data = Object.entries(data).reduce((acc, [key, value])=>Object.assign(acc, {
[key]: this.trackPromise(key, value)
}), {});
if (this.done) this.unlistenAbortSignal();
this.init = responseInit;
}
}
const activeDeferreds = new Map();
export { AbortedDeferredError, DeferredData, activeDeferreds, invariant };