Proposal: Allow Promise callbacks to be removed
Andrea Giammarchi
andrea.giammarchi at gmail.com
Tue Apr 24 12:46:14 UTC 2018
to be honest, I have solved already these cases through named promises and
the broadcast micro library I've mentioned.
```js
let shouldAsk = true;
function askForExpensiveTask() {
if (shouldAsk) {
shouldAsk = false;
doExpensiveThing().then(r => broadcast.that('expensive-task', r));
}
return broadcast.when('expensive-task');
}
```
That gives me the ability to name any task I want and resolve those asking
for such tasks whenever these are available.
A further call to `broadcast.that('expensive-task', other)` would update
the resolved value and notify those that setup
`broadcast.all('expensive-task', callback)`, which you can also `.drop()`
at any time.
Yet, having a way to hook into these kind of flows in core would be great.
Regards
On Tue, Apr 24, 2018 at 12:40 PM, kai zhu <kaizhu256 at gmail.com> wrote:
> 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 - piggyback gzipping of files onto a cacheable-task
> [2] 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> 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
> https://mail.mozilla.org/listinfo/es-discuss
>
>
>
> _______________________________________________
> es-discuss mailing list
> 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/5db3f150/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/5db3f150/attachment-0001.jpg>
More information about the es-discuss
mailing list