Skip to content

Instantly share code, notes, and snippets.

@iwan-uschka
Last active December 31, 2020 08:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iwan-uschka/99e589a8fa98f119f509464487f62282 to your computer and use it in GitHub Desktop.
Save iwan-uschka/99e589a8fa98f119f509464487f62282 to your computer and use it in GitHub Desktop.
create repetitive async callback
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();
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