// @flow import type { Condition, PixieInput, PixieInstance, TamePixie, TamePixieInput, UpdateFunction, WildPixie } from '../types.js' function makePixieShutdownError() { const e = new Error('Pixie has been destroyed') e.name = 'PixieShutdownError' return e } export function isPixieShutdownError(e: any) { return e instanceof Error && e.name === 'PixieShutdownError' } /** * If a wild pixie returns a bare function, turn that into a proper object. */ function fixInstance
( instance: PixieInstance
| UpdateFunction
): PixieInstance
{ if (typeof instance === 'function') { return { update: instance, destroy() {} } } return instance } /** * Catches synchronous errors and sends them through `onError`, * terminating the inner pixie in response. Also prevents `update` * from running in parallel if if returns a promise. */ export function babysitPixie
(wildPixie: WildPixie
): TamePixie
{ function outPixie(input: TamePixieInput) { let instance: PixieInstance
| void let propsCache: P let propsDirty: boolean = true let updating: boolean = false let destroyed: boolean = false let nextPromise: Promise
| void let rejector: ((e: any) => void) | void let resolver: ((props: P) => void) | void function destroy() { if (instance) { try { if (rejector) { const copy = rejector nextPromise = undefined rejector = undefined resolver = undefined copy(makePixieShutdownError()) } const copy = instance instance = undefined copy.destroy() } catch (e) { onError(e) } destroyed = true } } // Ignore any callbacks once `destroy` has completed: function onError(e: Error) { if (!destroyed) input.onError(e) destroy() } function onOutput(data: any) { if (!destroyed) input.onOutput(data) } function onUpdateDone() { updating = false tryUpdate() } function tryUpdate() { // eslint-disable-next-line no-unmodified-loop-condition while (instance && propsDirty && !updating) { propsDirty = false updating = true try { const thenable = instance.update(propsCache) if (thenable && typeof thenable.then === 'function') { thenable.then(onUpdateDone, onError) } else { updating = false } } catch (e) { onError(e) } } } function getNextPromise(): Promise
{ if (!nextPromise) { nextPromise = new Promise((resolve, reject) => { resolver = resolve rejector = reject }) } return nextPromise } const childInput: PixieInput
= {
onError,
onOutput,
get props() {
return propsCache
},
nextProps: getNextPromise,
waitFor ): Promise (pixie: WildPixie ): TamePixie {
return pixie.tame ? (pixie: any) : babysitPixie(pixie)
}