@stackbit/utils
Version:
Stackbit utilities
73 lines (64 loc) • 2.32 kB
text/typescript
import _ from 'lodash';
import { deferredPromise, DeferredPromise } from './promise-utils';
type EnqueuedJob = {
jobId: string;
jobFn: () => Promise<any>;
deferred: DeferredPromise<unknown>;
nextJobFn: (() => Promise<any>) | null;
};
export class Worker {
private promise: Promise<any>;
private enqueued: EnqueuedJob[];
constructor() {
this.promise = Promise.resolve();
this.enqueued = [];
}
schedule(jobFn: () => Promise<any>): Promise<any> {
return new Promise((resolve, reject) => {
this.promise = this.promise.finally(() => {
const res = jobFn();
if (res && typeof _.get(res, 'then') === 'function') {
return res.then(resolve).catch(reject);
} else {
resolve(res);
}
});
});
}
/**
* Run one after another 2 calls of same jobFn, 3rd and next calls will be waiting for finishing 2nd and
* respond all together
* In case there are another jobFn called on the same worker - 2nd round of calls would be postponed to process next call
* to prevent dead lock
* @see https://user-images.githubusercontent.com/97896/90123842-1fea2f80-dd68-11ea-8462-d4f1cdc1749b.png
* @param {String} jobId
* @param {Function} jobFn
* @returns {Promise}
*/
enqueueOnce(jobId: string, jobFn: () => Promise<any>): Promise<any> {
let enqueued = this.enqueued.find((obj) => obj.jobId === jobId);
if (enqueued) {
if (!enqueued.nextJobFn) {
enqueued.nextJobFn = jobFn;
}
return enqueued.deferred.promise;
}
enqueued = {
jobId,
jobFn,
deferred: deferredPromise(),
nextJobFn: null
};
this.enqueued.push(enqueued);
return this.schedule(jobFn).finally(() => {
if (!enqueued) {
return;
}
const index = this.enqueued.indexOf(enqueued);
this.enqueued.splice(index, 1);
if (enqueued.nextJobFn) {
this.enqueueOnce(enqueued.jobId, enqueued.nextJobFn).then(enqueued.deferred.resolve).catch(enqueued.deferred.reject);
}
});
}
}