Last active
December 31, 2020 08:58
-
-
Save iwan-uschka/99e589a8fa98f119f509464487f62282 to your computer and use it in GitHub Desktop.
create repetitive async callback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import createRepetitiveAsyncCallback from './createRepetitiveAsyncCallback'; | |
const repetitiveAsyncCallbackInstance = createRepetitiveAsyncCallback({ | |
asyncCallback: () => { | |
return doSomeAsyncStuffWeNeedToWaitFor(); | |
}, | |
interval: { | |
duration: 5000, | |
type: 'placid', | |
}, | |
onStart: () => { | |
doSomeStuffOnStart(); | |
}, | |
onRepeat: () => { | |
doSomeStuffOnRepeat(); | |
}, | |
onSuccess: (response) => { | |
doSomeStuffOnSuccess(response); | |
}, | |
onError: (error) => { | |
doSomeStuffOnError(error); | |
}, | |
}); | |
repetitiveAsyncCallbackInstance.start(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
// config.interval.type | |
// - strict: cancel previous run if necessary after timeout (interval duration) and start a new one | |
// - placid: wait for previous run to complete and start timeout (interval duration) afterwards | |
// - efficient: wait for previous run to complete and wait for rest of timeout (interval duration) if necessary | |
const createRepetitiveAsyncCallback = (configFromParams) => { | |
let running = false; | |
let config; | |
let intervalWaitTimeout; | |
let startTs; | |
let cancelRepetitiveAsyncCallbackResolve; | |
setConfig(configFromParams); | |
async function runAsyncCallback() { | |
return new Promise(async (resolve) => { | |
try { | |
const result = await config?.asyncCallback(); | |
resolve({ | |
response: result, | |
}); | |
} catch (error) { | |
resolve({ | |
error: error, | |
}); | |
} | |
}); | |
} | |
async function createIntervalWaitPromise(resolveResponse) { | |
return config.interval.duration > 0 | |
? new Promise((resolve) => { | |
intervalWaitTimeout = setTimeout( | |
resolve, | |
config.interval.duration, | |
resolveResponse, | |
); | |
}) | |
: new Promise(() => {}); | |
} | |
async function createCancelPromise() { | |
return new Promise((resolve) => { | |
cancelRepetitiveAsyncCallbackResolve = resolve; | |
}); | |
} | |
async function* runAsyncCallbackRepetitively(onRepeat) { | |
let callOnRepeat = false; | |
let canceled = false; | |
while (running && !canceled) { | |
clearTimeout(intervalWaitTimeout); | |
const asyncCallbackPromise = runAsyncCallback(); | |
const intervalWaitPromise = createIntervalWaitPromise({ | |
timeout: true, | |
}); | |
const neverResolvingPromise = new Promise(() => {}); | |
const cancelCallbackPromise = createCancelPromise(); | |
const beforeRequestTs = performance.now(); | |
if (callOnRepeat && onRepeat instanceof Function) { | |
onRepeat(); | |
} | |
callOnRepeat = true; | |
const result = await Promise.race([ | |
asyncCallbackPromise, | |
cancelCallbackPromise, | |
config.interval.type === 'strict' | |
? intervalWaitPromise | |
: neverResolvingPromise, | |
]); | |
if (result === '__CREATE_REPETITIVE_ASYNC_CALLBACK_CANCEL__') { | |
canceled = true; | |
yield result; | |
} else { | |
if (running && beforeRequestTs > startTs) { | |
yield result; | |
if (config.interval.type === 'placid') { | |
// start waiting | |
await createIntervalWaitPromise(); | |
} else if (!result.timeout) { | |
// continue waiting | |
await intervalWaitPromise; | |
} | |
} | |
} | |
} | |
} | |
function onResult(result) { | |
if (config.onSuccess instanceof Function && result?.response) { | |
config.onSuccess(result.response); | |
} else if (config.onError instanceof Function && result?.error) { | |
config.onError(result.error); | |
} else if (config.onTimeout instanceof Function && result?.timeout) { | |
config.onTimeout(); | |
} else if ( | |
config.onCancel instanceof Function && | |
result === '__CREATE_REPETITIVE_ASYNC_CALLBACK_CANCEL__' | |
) { | |
config.onCancel(); | |
} | |
} | |
async function start() { | |
stop(true); | |
startTs = performance.now(); | |
running = true; | |
if (config.onStart instanceof Function) { | |
config.onStart(); | |
} | |
if (config.interval.duration > 0) { | |
for await (let result of runAsyncCallbackRepetitively(config?.onRepeat)) { | |
onResult(result); | |
} | |
} else { | |
const asyncCallbackPromise = runAsyncCallback(); | |
const cancelCallbackPromise = createCancelPromise(); | |
const result = await Promise.race([ | |
asyncCallbackPromise, | |
cancelCallbackPromise, | |
]); | |
onResult(result); | |
} | |
} | |
function stop(calledBeforeStart) { | |
clearTimeout(intervalWaitTimeout); | |
if (cancelRepetitiveAsyncCallbackResolve instanceof Function) { | |
cancelRepetitiveAsyncCallbackResolve( | |
'__CREATE_REPETITIVE_ASYNC_CALLBACK_CANCEL__', | |
); | |
} | |
if (config.onStop instanceof Function) { | |
config.onStop(calledBeforeStart); | |
} | |
running = false; | |
} | |
function setConfig(configFromParams) { | |
config = { | |
...configFromParams, | |
interval: { | |
duration: 0, | |
type: 'strict', | |
returnTimeout: false, | |
...configFromParams?.interval, | |
}, | |
}; | |
} | |
function updateConfig(configFromParams) { | |
_.merge(config, configFromParams); | |
} | |
function getConfig() { | |
return config; | |
} | |
return { | |
start: start, | |
stop: stop, | |
setConfig: setConfig, | |
updateConfig: updateConfig, | |
getConfig: getConfig, | |
}; | |
}; | |
export default createRepetitiveAsyncCallback; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment