UNPKG

next

Version:

The React Framework

123 lines (122 loc) 5.65 kB
import PromiseQueue from 'next/dist/compiled/p-queue'; import { InvariantError } from '../../shared/lib/invariant-error'; import { isThenable } from '../../shared/lib/is-thenable'; import { workAsyncStorage } from '../app-render/work-async-storage.external'; import { withExecuteRevalidates } from '../revalidation-utils'; import { bindSnapshot } from '../app-render/async-local-storage'; import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'; import { afterTaskAsyncStorage } from '../app-render/after-task-async-storage.external'; export class AfterContext { constructor({ waitUntil, onClose, onTaskError }){ this.workUnitStores = new Set(); this.waitUntil = waitUntil; this.onClose = onClose; this.onTaskError = onTaskError; this.callbackQueue = new PromiseQueue(); this.callbackQueue.pause(); } after(task) { if (isThenable(task)) { if (!this.waitUntil) { errorWaitUntilNotAvailable(); } this.waitUntil(task.catch((error)=>this.reportTaskError('promise', error))); } else if (typeof task === 'function') { // TODO(after): implement tracing this.addCallback(task); } else { throw Object.defineProperty(new Error('`after()`: Argument must be a promise or a function'), "__NEXT_ERROR_CODE", { value: "E50", enumerable: false, configurable: true }); } } addCallback(callback) { // if something is wrong, throw synchronously, bubbling up to the `after` callsite. if (!this.waitUntil) { errorWaitUntilNotAvailable(); } const workUnitStore = workUnitAsyncStorage.getStore(); if (workUnitStore) { this.workUnitStores.add(workUnitStore); } const afterTaskStore = afterTaskAsyncStorage.getStore(); // This is used for checking if request APIs can be called inside `after`. // Note that we need to check the phase in which the *topmost* `after` was called (which should be "action"), // not the current phase (which might be "after" if we're in a nested after). // Otherwise, we might allow `after(() => headers())`, but not `after(() => after(() => headers()))`. const rootTaskSpawnPhase = afterTaskStore ? afterTaskStore.rootTaskSpawnPhase // nested after : workUnitStore == null ? void 0 : workUnitStore.phase // topmost after ; // this should only happen once. if (!this.runCallbacksOnClosePromise) { this.runCallbacksOnClosePromise = this.runCallbacksOnClose(); this.waitUntil(this.runCallbacksOnClosePromise); } // Bind the callback to the current execution context (i.e. preserve all currently available ALS-es). // We do this because we want all of these to be equivalent in every regard except timing: // after(() => x()) // after(x()) // await x() const wrappedCallback = bindSnapshot(async ()=>{ try { await afterTaskAsyncStorage.run({ rootTaskSpawnPhase }, ()=>callback()); } catch (error) { this.reportTaskError('function', error); } }); this.callbackQueue.add(wrappedCallback); } async runCallbacksOnClose() { await new Promise((resolve)=>this.onClose(resolve)); return this.runCallbacks(); } async runCallbacks() { if (this.callbackQueue.size === 0) return; for (const workUnitStore of this.workUnitStores){ workUnitStore.phase = 'after'; } const workStore = workAsyncStorage.getStore(); if (!workStore) { throw Object.defineProperty(new InvariantError('Missing workStore in AfterContext.runCallbacks'), "__NEXT_ERROR_CODE", { value: "E547", enumerable: false, configurable: true }); } return withExecuteRevalidates(workStore, ()=>{ this.callbackQueue.start(); return this.callbackQueue.onIdle(); }); } reportTaskError(taskKind, error) { // TODO(after): this is fine for now, but will need better intergration with our error reporting. // TODO(after): should we log this if we have a onTaskError callback? console.error(taskKind === 'promise' ? `A promise passed to \`after()\` rejected:` : `An error occurred in a function passed to \`after()\`:`, error); if (this.onTaskError) { // this is very defensive, but we really don't want anything to blow up in an error handler try { this.onTaskError == null ? void 0 : this.onTaskError.call(this, error); } catch (handlerError) { console.error(Object.defineProperty(new InvariantError('`onTaskError` threw while handling an error thrown from an `after` task', { cause: handlerError }), "__NEXT_ERROR_CODE", { value: "E569", enumerable: false, configurable: true })); } } } } function errorWaitUntilNotAvailable() { throw Object.defineProperty(new Error('`after()` will not work correctly, because `waitUntil` is not available in the current environment.'), "__NEXT_ERROR_CODE", { value: "E91", enumerable: false, configurable: true }); } //# sourceMappingURL=after-context.js.map