singleton.js 3.18 KB
Newer Older
YazhouChen's avatar
YazhouChen committed
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 108 109 110 111
'use strict'

const BaseConstitutor = require('./base')
const BaseResolver = require('../resolvers/base')
const InstanceResolver = require('../resolvers/instance')

const INSTANCE_MAP = Symbol('constitute:instance_map')

class SingletonConstitutor extends BaseConstitutor {
  constructor (constituents) {
    super()
    this.constituents = constituents.map(function (constituent) {
      if (constituent instanceof BaseResolver) {
        return constituent
      }

      return InstanceResolver.of(constituent)
    })
  }

  /**
   * Create a constitutor from the value the caller provided.
   *
   * When passed an array, we create a new SingletonConstitutor using provided
   * array as the dependencies.
   *
   * When passed an existing constitutor, we simply pass it through..
   *
   * When passed a falsy value, we create a default constitutor which is a
   * singleton with no dependencies.
   *
   * Otherwise, we assume something has gone horribly wrong and throw an Error.
   */
  static create (constitutor, contextHint) {
    if (typeof constitutor === 'function') {
      constitutor = constitutor()

      if (Array.isArray(constitutor)) {
        return SingletonConstitutor.with(constitutor)
      } else if (constitutor instanceof BaseConstitutor) {
        return constitutor
      }

      // Not a valid return value
      const context = contextHint
        ? 'The constitute annotation in ' + contextHint
        : 'A constitute annotation'
      throw new Error(
        context + ' returned an invalid value ' +
        'of type ' + (typeof constitutor) + ' (should have been an ' +
        'array or a constitutor function)')
    } else if (Array.isArray(constitutor)) {
      // Use default constitutor
      return SingletonConstitutor.with(constitutor)
    } else if (constitutor instanceof BaseConstitutor) {
      return constitutor
    } else if (!constitutor) {
      return SingletonConstitutor.with([])
    } else {
      throw new Error('Invalid constitutor of type ' + typeof constitutor)
    }
  }

  static getInstanceCache (container) {
    if (!container[INSTANCE_MAP]) {
      container[INSTANCE_MAP] = new Map()
    }
    return container[INSTANCE_MAP]
  }

  static getCachedInstance (container, key) {
    const cachedInstance = this.getInstanceCache(container).get(key)

    // Check parent's cache
    if (!cachedInstance && container._parent) {
      return this.getCachedInstance(container._parent, key)
    }

    return cachedInstance
  }

  static setCachedInstance (container, key, instance) {
    return this.getInstanceCache(container).set(key, instance)
  }

  static with (constituents) {
    return new this(constituents)
  }

  constitute (container, key, fn) {
    let instance = this.constructor.getCachedInstance(container, key)
    if (instance) {
      return instance
    }

    const params = this.constituents.map(function (constituent) {
      return constituent.resolve(container)
    })
    instance = Function.prototype.call.call(fn, null, params)

    this.constructor.setCachedInstance(container, key, instance)

    return instance
  }

  getCachedInstance (container, key) {
    return this.constructor.getCachedInstance(container, key)
  }
}

module.exports = SingletonConstitutor