Skip to content

Instantly share code, notes, and snippets.

@kalda341
Created November 12, 2019 21:03
Show Gist options
  • Select an option

  • Save kalda341/d59a4e7de9d8e144e9579b4e50326ef9 to your computer and use it in GitHub Desktop.

Select an option

Save kalda341/d59a4e7de9d8e144e9579b4e50326ef9 to your computer and use it in GitHub Desktop.
const Task = (func, args) => {
const taskState = {
isRunning: false,
isCancelled: false,
isFinished: false,
isError: false,
result: null,
error: null
};
let resolve = null;
let reject = null;
const promise = new Promise((innerResolve, innerReject) => {
resolve = innerResolve;
reject = innerReject;
});
const start = () => {
const success = result => {
taskState.isRunning = false;
taskState.isCancelled = false;
taskState.isSuccess = true;
taskState.isError = false;
taskState.result = result;
taskState.error = null;
resolve(result);
};
const error = error => {
taskState.isRunning = false;
taskState.isCancelled = false;
taskState.isSuccess = false;
taskState.isError = true;
taskState.result = null;
taskState.error = error;
reject(error);
};
const cancelled = () => {
// The state should already be set by the cancel function
reject(new Error('Task was cancelled'));
};
// Immediately invoked function
(async () => {
// Handle cancel before starting generator
if (taskState.isCancelled) {
return cancelled();
}
const generator = func(...args);
let currentValue = null;
// eslint-disable-next-line no-constant-condition
while (true) {
try {
const intermediateResult = generator.next(currentValue);
currentValue = await intermediateResult.value;
// Handle cancel check after any successful async operation
if (taskState.isCancelled) {
return cancelled();
} else if (intermediateResult.done) {
return success(currentValue);
}
} catch (e) {
// Handle cancel check after any unsuccessful async operation
if (taskState.isCancelled) {
return cancelled(e);
}
// Let the task function handle the error if it can
try {
generator.throw(e);
} catch (e) {
return error(e);
}
}
}
})();
return promise;
};
const cancel = () => {
if (!taskState.isFinished && !taskState.isError) {
taskState.isRunning = false;
taskState.isCancelled = true;
taskState.isSuccess = false;
taskState.isError = false;
taskState.result = null;
taskState.error = null;
}
};
// Actions
taskState.start = start;
taskState.cancel = cancel;
// Promise functions
taskState.catch = promise.catch.bind(promise);
taskState.then = promise.then.bind(promise);
taskState.finally = promise.finally.bind(promise);
return taskState;
};
const TaskFactory = (func, strategy) => {
let queuedTasks = [];
let runningTasks = [];
let isRunning = false;
const setRunningTasks = tasks => {
runningTasks = tasks;
isRunning = runningTasks.length !== 0;
}
const queueTask = task => {
queuedTasks = append(task, queuedTasks);
}
const maybeStartQueuedTask = () => {
if (queuedTasks.length && (strategy != 'ENQUEUE' || !isRunning)) {
const task = queuedTasks[0];
// Note - compare by refrence
queuedTasks = reject(t => t === task, queuedTasks);
setRunningTasks(append(task, runningTasks));
task.start();
}
}
const taskFinished = task => {
// Note - compare by refrence
setRunningTasks(reject(t => t === task, runningTasks));
maybeStartQueuedTask();
}
const perform = (...args) => performFunc(func, args);
const performFunc = (func, args) => {
const task = Task(func, args);
if (strategy === 'RESTARTABLE') {
cancelAll();
} else if (strategy === 'DROP' && isRunning) {
task.cancel();
return task;
}
queueTask(task);
maybeStartQueuedTask();
task
// Prevent uncaught promise error
.catch(() => {})
.finally(() => {
taskFinished(task);
});
return task;
};
const cancelAll = () => {
queuedTasks = [];
forEach(task => task.cancel(), runningTasks);
}
return {
perform,
performFunc,
cancelAll,
};
}
export const useTask = (func, strategy) => {
const factoryRef = useRef(TaskFactory(null, strategy));
const factory = factoryRef.current;
// Cancel all on unmount
useEffect(() => {
return () => {
factory.cancelAll();
}
}, []);
// Override perform to always use the most recent function
return merge(
factory,
{ perform: (...args) => factory.performFunc(func, args) }
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment