d3-queue
d3-queue copied to clipboard
Allow "defer after await" at any time before notification triggered
I realized today that some utility scripts I wrote are no longer working since the current version explicitly checks for and throws on defer after await. Reviewing when/why this might have gotten enforced it looks like I have a history of beating this drum already!
My use case boils down to essentially cases of "pagination" or "following linked lists asyncronously". I.e. the task I'm enqueuing does some work and only in the process of doing said work determines whether more work needs to be done. Thus my pattern is to create a queue, defer the initial task, and immediately awaitAll the eventual results.
My current workaround is to hack around the assertion:
const {queue:q} = require('d3-queue');
const _actualDefer = q.prototype.defer;
q.prototype.defer = function () {
var _origCall = this._call;
this._call = null;
var result = _actualDefer.apply(this, arguments);
this._call = _origCall;
return result;
};
My proposal would be that defer can be called at any time before the notification actually happens. Likely implementation would be something like:
const TOMBSTONE = Symbol("await called"); // NOTE: `Object.create(null);` for older JS engines would suffice
function maybeNotify(q) {
if (!q._active && q._call) {
var d = q._data;
q._data = undefined; // allow gc
q._call(q._error, d);
q._call = TOMBSTONE;
}
}
and replacing
if (this._call) throw new Error("defer after await");
with
if (this._call === TOMBSTONE) throw new Error("defer after await called");`
I can submit a PR including any ancillary doc/test changes if you'd be amenable to this behavior change.
(Additional background)
The original discussions are all mixed up in some ancient stuff about zalgo and this passing, but for posterity:
- https://github.com/d3/d3-queue/issues/9#issuecomment-15812100
- https://github.com/d3/d3-queue/issues/7#issuecomment-15849613
The concern seemed to be primarily a documentation/specification issue. I guess outlawing the use case entirely simplifies that, but it prevents advanced usage. (Note: current me is not super sold on my original proposal where "the await/awaitAll callback is called every time the number of remaining tasks goes to 0" either as it would indeed be a pain to explain at best…)
I’m not maintaining this library anymore. I recommend using promises and async/await.