Proposal: Allow Promise callbacks to be removed
kai zhu
kaizhu256 at gmail.com
Tue Apr 24 10:42:11 UTC 2018
> I see a simple scenario like the following one:
> user asks for a very expensive task clicking section A
> while it's waiting for it, user changes idea clicking section B
> both section A and section B needs that very expensive async call
> drop "going to section A" info and put "go to section B" to that very same promise
> whenever resolved, do that action
> A caching mechanism to trigger only once such expensive operation would also work, yet it's not possible to drop "go into A" and put "go into B”
for your scenario, what you want is a cacheable background-task, where you can piggyback B onto the task initiated by A (e.g. common-but-expensive database-queries that might take 10-60 seconds to execute).
its generally more trouble than its worth to micromanage such tasks with removeListeners or make them cancellable (maybe later on C wants to piggyback, even tho A and B are no longer interested). its easier implementation-wise to have the background-task run its course and save it to cache, and just have A ignore the results. the logic is that because this common-but-expensive task was recently called, it will likely be called again in the near-future, so let it run its course and cache the result.
here's a real-world cacheable-task implementation for such a scenario, but it piggybacks the expensive gzipping of commonly-requested files, instead of database-queries [1] [2]
[1] https://github.com/kaizhu256/node-utility2/blob/2018.1.13/lib.utility2.js#L4372 <https://github.com/kaizhu256/node-utility2/blob/2018.1.13/lib.utility2.js#L4372> - piggyback gzipping of files onto a cacheable-task
[2] https://github.com/kaizhu256/node-utility2/blob/2018.1.13/lib.utility2.js#L5872 <https://github.com/kaizhu256/node-utility2/blob/2018.1.13/lib.utility2.js#L5872> - cacheable-task source-code
```js
/*jslint
bitwise: true,
browser: true,
maxerr: 4,
maxlen: 100,
node: true,
nomen: true,
regexp: true,
stupid: true
*/
local.middlewareAssetsCached = function (request, response, nextMiddleware) {
/*
* this function will run the middleware that will serve cached gzipped-assets
* 1. if cache-hit for the gzipped-asset, then immediately serve it to response
* 2. run background-task (if not already) to re-gzip the asset and update cache
* 3. save re-gzipped-asset to cache
* 4. if cache-miss, then piggy-back onto the background-task
*/
var options;
options = {};
local.onNext(options, function (error, data) {
options.result = options.result || local.assetsDict[request.urlParsed.pathname];
if (options.result === undefined) {
nextMiddleware(error);
return;
}
switch (options.modeNext) {
case 1:
// skip gzip
if (response.headersSent ||
!(/\bgzip\b/).test(request.headers['accept-encoding'])) {
options.modeNext += 1;
options.onNext();
return;
}
// gzip and cache result
local.taskCreateCached({
cacheDict: 'middlewareAssetsCachedGzip',
key: request.urlParsed.pathname
}, function (onError) {
local.zlib.gzip(options.result, function (error, data) {
onError(error, !error && data.toString('base64'));
});
}, options.onNext);
break;
case 2:
// set gzip header
options.result = local.base64ToBuffer(data);
response.setHeader('Content-Encoding', 'gzip');
response.setHeader('Content-Length', options.result.length);
options.onNext();
break;
case 3:
local.middlewareCacheControlLastModified(request, response, options.onNext);
break;
case 4:
response.end(options.result);
break;
}
});
options.modeNext = 0;
options.onNext();
};
...
local.taskCreateCached = function (options, onTask, onError) {
/*
* this function will
* 1. if cache-hit, then call onError with cacheValue
* 2. run onTask in background to update cache
* 3. save onTask's result to cache
* 4. if cache-miss, then call onError with onTask's result
*/
local.onNext(options, function (error, data) {
switch (options.modeNext) {
// 1. if cache-hit, then call onError with cacheValue
case 1:
// read cacheValue from memory-cache
local.cacheDict[options.cacheDict] = local.cacheDict[options.cacheDict] ||
{};
options.cacheValue = local.cacheDict[options.cacheDict][options.key];
if (options.cacheValue) {
// call onError with cacheValue
options.modeCacheHit = true;
onError(null, JSON.parse(options.cacheValue));
if (!options.modeCacheUpdate) {
break;
}
}
// run background-task with lower priority for cache-hit
setTimeout(options.onNext, options.modeCacheHit && options.cacheTtl);
break;
// 2. run onTask in background to update cache
case 2:
local.taskCreate(options, onTask, options.onNext);
break;
default:
// 3. save onTask's result to cache
// JSON.stringify data to prevent side-effects on cache
options.cacheValue = JSON.stringify(data);
if (!error && options.cacheValue) {
local.cacheDict[options.cacheDict][options.key] = options.cacheValue;
}
// 4. if cache-miss, then call onError with onTask's result
if (!options.modeCacheHit) {
onError(error, options.cacheValue && JSON.parse(options.cacheValue));
}
(options.onCacheWrite || local.nop)();
break;
}
});
options.modeNext = 0;
options.onNext();
};
```
> On 24 Apr 2018, at 6:06 PM, Oliver Dunk <oliver at oliverdunk.com <mailto:oliver at oliverdunk.com>> wrote:
>
> Based on feedback, I agree that a blanket `Promise.prototype.clear()` is a bad idea. I don’t think that is worth pursuing.
>
> I still think that there is value in this, especially the adding and removing of listeners you have reference to as Andrea’s PoC shows. Listeners would prevent the chaining issue or alternatively I think it would definitely be possible to decide on intuitive behaviour with the clear mechanic. The benefit of `clear(reference)` over listeners is that it adds less to the semantics.
>
> I think the proposed userland solutions are bigger than I would want for something that I believe should be available by default, but I respect that a lot of the people in this conversation are in a better position to make a judgement about that than me.
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
> https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180424/73f1cb83/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screen Shot 2018-04-24 at 5.31.58 PM copy.jpg
Type: image/jpeg
Size: 68324 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180424/73f1cb83/attachment-0001.jpg>
More information about the es-discuss
mailing list