/* global clearTimeout, log */ // was timeoutDelay const sleepFor = ms => new Promise(resolve => setTimeout(resolve, ms)); // Taken from https://stackoverflow.com/questions/51160260/clean-way-to-wait-for-first-true-returned-by-promise // The promise returned by this function will resolve true when the first promise // in ps resolves true *or* it will resolve false when all of ps resolve false const firstTrue = ps => { const newPs = ps.map( p => new Promise( // eslint-disable-next-line more/no-then (resolve, reject) => p.then(v => v && resolve(v), reject) ) ); // eslint-disable-next-line more/no-then newPs.push(Promise.all(ps).then(() => false)); return Promise.race(newPs); }; // one action resolves all const snodeGlobalLocks = {}; async function allowOnlyOneAtATime(name, process, timeout) { // if currently not in progress if (snodeGlobalLocks[name] === undefined) { // set lock snodeGlobalLocks[name] = new Promise(async (resolve, reject) => { // set up timeout feature let timeoutTimer = null; if (timeout) { timeoutTimer = setTimeout(() => { log.warn( `loki_primitives:::allowOnlyOneAtATime - TIMEDOUT after ${timeout}s` ); delete snodeGlobalLocks[name]; // clear lock reject(); }, timeout); } // do actual work let innerRetVal; try { innerRetVal = await process(); } catch (e) { log.error( `loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}` ); // clear timeout timer if (timeout) { if (timeoutTimer !== null) { clearTimeout(timeoutTimer); timeoutTimer = null; } } delete snodeGlobalLocks[name]; // clear lock throw e; } // clear timeout timer if (timeout) { if (timeoutTimer !== null) { clearTimeout(timeoutTimer); timeoutTimer = null; } } delete snodeGlobalLocks[name]; // clear lock // release the kraken resolve(innerRetVal); }); } return snodeGlobalLocks[name]; } function abortableIterator(array, iterator) { let abortIteration = false; // for the control promise let controlResolveFunctor; const stopPolling = new Promise(res => { // store resolve functor controlResolveFunctor = res; }); // eslint-disable-next-line more/no-then stopPolling.then(() => { abortIteration = true; }); const destructableList = [...array]; const accum = []; return { start: async serially => { let item = destructableList.pop(); while (item && !abortIteration) { if (serially) { // eslint-disable-next-line no-await-in-loop accum.push(await iterator(item)); } else { accum.push(iterator(item)); } item = destructableList.pop(); } return accum; }, stop: () => { controlResolveFunctor(); }, }; } module.exports = { sleepFor, allowOnlyOneAtATime, abortableIterator, firstTrue, };