UNPKG

@modern-js/runtime-utils

Version:

A Progressive React Framework for modern web development.

120 lines (119 loc) 4.55 kB
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 };