singleton.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. 'use strict';
  2. const assert = require('assert');
  3. const is = require('is-type-of');
  4. class Singleton {
  5. constructor(options = {}) {
  6. assert(options.name, '[egg:singleton] Singleton#constructor options.name is required');
  7. assert(options.app, '[egg:singleton] Singleton#constructor options.app is required');
  8. assert(options.create, '[egg:singleton] Singleton#constructor options.create is required');
  9. assert(!options.app[options.name], `${options.name} is already exists in app`);
  10. this.clients = new Map();
  11. this.app = options.app;
  12. this.name = options.name;
  13. this.create = options.create;
  14. /* istanbul ignore next */
  15. this.options = options.app.config[this.name] || {};
  16. }
  17. init() {
  18. return is.asyncFunction(this.create) ? this.initAsync() : this.initSync();
  19. }
  20. initSync() {
  21. const options = this.options;
  22. assert(!(options.client && options.clients),
  23. `egg:singleton ${this.name} can not set options.client and options.clients both`);
  24. // alias app[name] as client, but still support createInstance method
  25. if (options.client) {
  26. const client = this.createInstance(options.client, options.name);
  27. this.app[this.name] = client;
  28. this._extendDynamicMethods(client);
  29. return;
  30. }
  31. // multi clent, use app[name].getInstance(id)
  32. if (options.clients) {
  33. Object.keys(options.clients).forEach(id => {
  34. const client = this.createInstance(options.clients[id], id);
  35. this.clients.set(id, client);
  36. });
  37. this.app[this.name] = this;
  38. return;
  39. }
  40. // no config.clients and config.client
  41. this.app[this.name] = this;
  42. }
  43. async initAsync() {
  44. const options = this.options;
  45. assert(!(options.client && options.clients),
  46. `egg:singleton ${this.name} can not set options.client and options.clients both`);
  47. // alias app[name] as client, but still support createInstance method
  48. if (options.client) {
  49. const client = await this.createInstanceAsync(options.client, options.name);
  50. this.app[this.name] = client;
  51. this._extendDynamicMethods(client);
  52. return;
  53. }
  54. // multi clent, use app[name].getInstance(id)
  55. if (options.clients) {
  56. await Promise.all(Object.keys(options.clients).map(id => {
  57. return this.createInstanceAsync(options.clients[id], id)
  58. .then(client => this.clients.set(id, client));
  59. }));
  60. this.app[this.name] = this;
  61. return;
  62. }
  63. // no config.clients and config.client
  64. this.app[this.name] = this;
  65. }
  66. get(id) {
  67. return this.clients.get(id);
  68. }
  69. createInstance(config, clientName) {
  70. // async creator only support createInstanceAsync
  71. assert(!is.asyncFunction(this.create),
  72. `egg:singleton ${this.name} only support create asynchronous, please use createInstanceAsync`);
  73. // options.default will be merge in to options.clients[id]
  74. config = Object.assign({}, this.options.default, config);
  75. return this.create(config, this.app, clientName);
  76. }
  77. async createInstanceAsync(config, clientName) {
  78. // options.default will be merge in to options.clients[id]
  79. config = Object.assign({}, this.options.default, config);
  80. return await this.create(config, this.app, clientName);
  81. }
  82. _extendDynamicMethods(client) {
  83. assert(!client.createInstance, 'singleton instance should not have createInstance method');
  84. assert(!client.createInstanceAsync, 'singleton instance should not have createInstanceAsync method');
  85. try {
  86. let extendable = client;
  87. // Object.preventExtensions() or Object.freeze()
  88. if (!Object.isExtensible(client) || Object.isFrozen(client)) {
  89. // eslint-disable-next-line no-proto
  90. extendable = client.__proto__ || client;
  91. }
  92. extendable.createInstance = this.createInstance.bind(this);
  93. extendable.createInstanceAsync = this.createInstanceAsync.bind(this);
  94. } catch (err) {
  95. this.app.logger.warn('egg:singleton %s dynamic create is disabled because of client is unextensible', this.name);
  96. }
  97. }
  98. }
  99. module.exports = Singleton;