Cache.js.flow 2.47 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const {Logger} = require('metro-core');

import type {CacheStore} from 'metro-cache';

/**
 * Main cache class. Receives an array of cache instances, and sequentially
 * traverses them to return a previously stored value. It also ensures setting
 * the value in all instances.
 *
 * All get/set operations are logged via Metro's logger.
 */
class Cache<T> {
  _stores: $ReadOnlyArray<CacheStore<T>>;

  _hits: WeakMap<Buffer, CacheStore<T>>;

  constructor(stores: $ReadOnlyArray<CacheStore<T>>) {
    this._hits = new WeakMap();
    this._stores = stores;
  }

  async get(key: Buffer): Promise<?T> {
    const stores = this._stores;
    const length = stores.length;

    for (let i = 0; i < length; i++) {
      const store = stores[i];
      const name = store.constructor.name + '::' + key.toString('hex');
      let value = null;

      const logStart = Logger.log(
        Logger.createActionStartEntry({
          action_name: 'Cache get',
          log_entry_label: name,
        }),
      );

      try {
        const valueOrPromise = store.get(key);

        if (valueOrPromise && typeof valueOrPromise.then === 'function') {
          value = await valueOrPromise;
        } else {
          value = valueOrPromise;
        }
      } finally {
        Logger.log(Logger.createActionEndEntry(logStart));

        Logger.log(
          Logger.createEntry({
            action_name: 'Cache ' + (value == null ? 'miss' : 'hit'),
            log_entry_label: name,
          }),
        );

        if (value != null) {
          this._hits.set(key, store);

          return value;
        }
      }
    }

    return null;
  }

  set(key: Buffer, value: T): void {
    const stores = this._stores;
    const stop = this._hits.get(key);
    const length = stores.length;
    const promises = [];

    for (let i = 0; i < length && stores[i] !== stop; i++) {
      const store = stores[i];
      const name = store.constructor.name + '::' + key.toString('hex');

      Logger.log(
        Logger.createEntry({
          action_name: 'Cache set',
          log_entry_label: name,
        }),
      );

      promises.push(stores[i].set(key, value));
    }

    Promise.all(promises).catch(err => {
      process.nextTick(() => {
        throw err;
      });
    });
  }
}

module.exports = Cache;