/**
* @namespace Utils
* @category Utils
* @module Promise*/
/**
* Generic helper for progresive searchs.
*
* Used to split heavy requests without pagination support (Geored APIV2).
*
* @param {Function} options.generateSubsets Handler to generate subsets
* @param {Function} options.handleSubsets Handler to process and resolve subsets
* @param {Function} options.withSubsetResult Handler to control a subset result
* @param {Boolean} options.sequential Subsets will resolve sequentially
*/
export async function splitOperation(options = {}) {
const sequential = require('promise-sequential')
let subsets = options.generateSubsets()
if (options.sequential === true) {
return await sequential(
subsets.map((subset) => {
return async () => {
let r = await options.handleSubset(subset)
options.withSubsetResult && options.withSubsetResult(r, subset)
return r
}
})
)
} else {
return await Promise.all(
subsets.map((subset) =>
(async () => {
let r = await options.handleSubset(subset)
options.withSubsetResult && options.withSubsetResult(r, subset)
return r
})()
)
)
}
}
/**
* Queue function to be called after certain time (like setTimeout) but skips if already queued.
*
* Useful for delaying operations in vue watchers/computed properties (To avoid multiple simultaneous calls and performance issues)
*
* This code is an async function called queueOperationOnce with 3 parameters (uniqueId, handler and millisecondsOrOptions). The function checkes if the parameters are valid and then starts a timeout that calls the specified handler. This code is used to ensure that only one instance of an operation runs at any given time - when another instance of the same operation is triggered, the new instance will be skipped and cleared or replaced, depending on the specified option.
*
* @param {String} uniqueId The unique identifier for the queue
* @param {Function} handler handler to be queued and executed after certain time.
* @param {Number} millisecondsOrOptions Timeout in milliseconds
* @param {Number} millisecondsOrOptions.timeout Timeout in milliseconds
* @param {Boolean} millisecondsOrOptions.isSequential Skip if similar operation is processing
* @param {Boolean} millisecondsOrOptions.clearPreviousTimeout If true, previous queued operations will be overrided
*/
export async function queueOperationOnce(
uniqueId,
handler,
millisecondsOrOptions = 1000
) {
if (!uniqueId) {
throw new Error('string expected (uniqueId)')
}
if (typeof handler !== 'function') {
throw new Error('function expected (handler)')
}
let options =
typeof millisecondsOrOptions !== 'object' ? {} : millisecondsOrOptions
let timeoutMilliseconds =
typeof millisecondsOrOptions !== 'object'
? millisecondsOrOptions
: millisecondsOrOptions.timeout || 1000
const globalStateWinPropName = '_queueOperations'
if (window[globalStateWinPropName] === undefined) {
window[globalStateWinPropName] = {}
}
const hasActiveTimeout = () =>
window[globalStateWinPropName][uniqueId] &&
window[globalStateWinPropName][uniqueId].windowTimeout
const removeActiveTimeout = () => {
if (hasActiveTimeout()) {
window.clearTimeout(
window[globalStateWinPropName][uniqueId].windowTimeout
)
delete window[globalStateWinPropName][uniqueId].windowTimeout
}
}
const createQueueScope = (obj) =>
(window[globalStateWinPropName][uniqueId] = obj)
const hasQueueScope = () => !!window[globalStateWinPropName][uniqueId]
const updateQueueScope = (attributeName, value) => {
if (hasQueueScope()) {
window[globalStateWinPropName][uniqueId][attributeName] = value
}
}
const isProcessing = () =>
hasQueueScope() && window[globalStateWinPropName][uniqueId]['processing']
const incrementQueueScopeValue = (attributeName) =>
window[globalStateWinPropName][uniqueId][attributeName]++
return new Promise((resolve, reject) => {
if (hasActiveTimeout()) {
if (options.clearPreviousTimeout) {
removeActiveTimeout() //Replace previous timeout
incrementQueueScopeValue('clearCount')
} else {
incrementQueueScopeValue('skipCount')
return resolve() //Skip if the same operation is already queued
}
}
if (options.isSequential && isProcessing()) {
incrementQueueScopeValue('skipAlreadyProcessingCount')
return resolve()
}
let windowTimeout = window.setTimeout(() => {
try {
updateQueueScope('processing', true)
let handlerResponse = handler()
if (handlerResponse instanceof Promise) {
handlerResponse
.then((response) => {
incrementQueueScopeValue('resolvedCount')
resolve(response)
options.resolve && options.resolve(handlerResponse)
})
.catch(reject)
.finally(() => {
updateQueueScope('processing', false)
removeActiveTimeout()
})
} else {
removeActiveTimeout()
incrementQueueScopeValue('resolvedCount')
updateQueueScope('processing', false)
resolve(handlerResponse)
options.resolve && options.resolve(handlerResponse)
}
} catch (err) {
removeActiveTimeout
reject(err)
}
}, timeoutMilliseconds)
if (!hasQueueScope()) {
createQueueScope({
uniqueId,
skipCount: 0,
skipAlreadyProcessingCount: 0,
clearCount: 0,
resolvedCount: 0,
windowTimeout,
processing: false,
})
} else {
updateQueueScope('windowTimeout', windowTimeout)
}
})
}
Source