|
| 1 | +import { isPromise } from '@sofie-automation/shared-lib/dist/lib/lib' |
1 | 2 | import { Meteor } from 'meteor/meteor' |
2 | 3 | import { Tracker } from 'meteor/tracker' |
3 | 4 | import _ from 'underscore' |
| 5 | +import { getRandomString } from './tempLib' |
4 | 6 |
|
5 | 7 | const isolatedAutorunsMem: { |
6 | 8 | [key: string]: { |
@@ -78,3 +80,86 @@ export function memoizedIsolatedAutorun<T extends (...args: any) => any>( |
78 | 80 | // @ts-expect-error it is assigned by the tracker |
79 | 81 | return result |
80 | 82 | } |
| 83 | + |
| 84 | +interface IsolatedAsyncAutorunState { |
| 85 | + computationId: string |
| 86 | + dependancy: Tracker.Dependency |
| 87 | + value: any |
| 88 | +} |
| 89 | + |
| 90 | +const isolatedAsyncAutorunsMem: { |
| 91 | + [key: string]: IsolatedAsyncAutorunState |
| 92 | +} = {} |
| 93 | + |
| 94 | +export async function memoizedIsolatedAutorunAsync<TArgs extends any[], TRes>( |
| 95 | + parentComputation: Tracker.Computation | null, |
| 96 | + fnc: (computation: Tracker.Computation, ...args: TArgs) => Promise<TRes>, |
| 97 | + functionName: string, |
| 98 | + ...params: TArgs |
| 99 | +): Promise<TRes> { |
| 100 | + function hashFncAndParams(fName: string, p: any): string { |
| 101 | + return fName + '_' + JSON.stringify(p) |
| 102 | + } |
| 103 | + |
| 104 | + const fId = hashFncAndParams(functionName, params) |
| 105 | + // Computation is already running, depend on it |
| 106 | + if (isolatedAsyncAutorunsMem[fId]) { |
| 107 | + const result = isolatedAsyncAutorunsMem[fId].value |
| 108 | + isolatedAsyncAutorunsMem[fId].dependancy.depend(parentComputation) |
| 109 | + |
| 110 | + return result |
| 111 | + } |
| 112 | + |
| 113 | + // Setup the computation |
| 114 | + const computationId = getRandomString() |
| 115 | + const dep = new Tracker.Dependency() |
| 116 | + dep.depend(parentComputation) |
| 117 | + const computation = Tracker.nonreactive(() => { |
| 118 | + const computationState: IsolatedAsyncAutorunState = { |
| 119 | + computationId, |
| 120 | + dependancy: dep, |
| 121 | + value: null, // Filled in later |
| 122 | + } |
| 123 | + |
| 124 | + const computation = Tracker.autorun(async (innerComputation) => { |
| 125 | + // Start executing the function |
| 126 | + const rawValue: Promise<TRes> = fnc(innerComputation, ...params) |
| 127 | + |
| 128 | + // Fetch the previous value and the new value |
| 129 | + const oldValue = computationState.value |
| 130 | + const newValue = await rawValue |
| 131 | + |
| 132 | + // If the old value is an unresolved promise, we can't compare it |
| 133 | + const oldRealValue = isPromise(oldValue) ? null : oldValue |
| 134 | + |
| 135 | + // If the values are different, invalidate the dependancy |
| 136 | + // Do this even for the first run, as other listeners might have joined while the promise was resolving |
| 137 | + if (!_.isEqual(oldRealValue, newValue)) { |
| 138 | + dep.changed() |
| 139 | + } |
| 140 | + |
| 141 | + return newValue as void // Tracker.autorun isn't generic |
| 142 | + }) |
| 143 | + computation.onStop(() => { |
| 144 | + // Only delete if it is this computation that is stopping |
| 145 | + if (isolatedAsyncAutorunsMem[fId]?.computationId === computationId) { |
| 146 | + delete isolatedAsyncAutorunsMem[fId] |
| 147 | + } |
| 148 | + }) |
| 149 | + |
| 150 | + // Store the first value |
| 151 | + computationState.value = computation.firstRunPromise |
| 152 | + isolatedAsyncAutorunsMem[fId] = computationState |
| 153 | + |
| 154 | + return computation |
| 155 | + }) |
| 156 | + const gc = Meteor.setInterval(() => { |
| 157 | + if (!dep.hasDependents()) { |
| 158 | + Meteor.clearInterval(gc) |
| 159 | + computation.stop() |
| 160 | + } |
| 161 | + }, 5000) |
| 162 | + |
| 163 | + // Return the promise of the first value |
| 164 | + return computation.firstRunPromise as TRes // Tracker.autorun isn't generic |
| 165 | +} |
0 commit comments