task-cache.js (2682B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { assert } = require("resource://devtools/shared/DevToolsUtils.js"); 7 8 /** 9 * The `TaskCache` allows for re-using active tasks when spawning a second task 10 * would simply duplicate work and is unnecessary. It maps from a task's unique 11 * key to the promise of its result. 12 */ 13 const TaskCache = (module.exports = class TaskCache { 14 constructor() { 15 this._cache = new Map(); 16 } 17 18 /** 19 * Get the promise keyed by the given unique `key`, if one exists. 20 * 21 * @param {Any} key 22 * @returns {Promise<Any> | undefined} 23 */ 24 get(key) { 25 return this._cache.get(key); 26 } 27 28 /** 29 * Put the task result promise in the cache and associate it with the given 30 * `key` which must not already have an entry in the cache. 31 * 32 * @param {Any} key 33 * @param {Promise<Any>} promise 34 */ 35 put(key, promise) { 36 assert(!this._cache.has(key), "We should not override extant entries"); 37 38 this._cache.set(key, promise); 39 } 40 41 /** 42 * Remove the cache entry with the given key. 43 * 44 * @param {Any} key 45 */ 46 remove(key) { 47 assert( 48 this._cache.has(key), 49 `Should have an extant entry for key = ${key}` 50 ); 51 52 this._cache.delete(key); 53 } 54 }); 55 56 /** 57 * Create a new action-orchestrating task that is automatically cached. The 58 * tasks themselves are responsible from removing themselves from the cache. 59 * 60 * @param {Function(...args) -> Any} getCacheKey 61 * @param {Generator(...args) -> Any} task 62 * 63 * @returns Cacheable, Action-Creating Task 64 */ 65 TaskCache.declareCacheableTask = function ({ getCacheKey, task }) { 66 const cache = new TaskCache(); 67 68 return function (...args) { 69 return async function ({ dispatch, getState }) { 70 const key = getCacheKey(...args); 71 72 const extantResult = cache.get(key); 73 if (extantResult) { 74 return extantResult; 75 } 76 77 // Ensure that we have our new entry in the cache *before* dispatching the 78 // task! 79 let resolve; 80 cache.put( 81 key, 82 new Promise(r => { 83 resolve = r; 84 }) 85 ); 86 87 resolve( 88 dispatch(async function () { 89 try { 90 args.push(() => cache.remove(key), dispatch, getState); 91 return await task(...args); 92 } catch (error) { 93 // Don't perma-cache errors. 94 if (cache.get(key)) { 95 cache.remove(key); 96 } 97 throw error; 98 } 99 }) 100 ); 101 102 return cache.get(key); 103 }; 104 }; 105 };