Skip to content

Commit

Permalink
feat(singleton): support async create function (#2382)
Browse files Browse the repository at this point in the history
  • Loading branch information
dead-horse authored Apr 14, 2018
1 parent a5b6731 commit 3a489b6
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 50 deletions.
27 changes: 22 additions & 5 deletions lib/core/singleton.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const assert = require('assert');
const is = require('is-type-of');

class Singleton {
constructor(options = {}) {
Expand All @@ -16,25 +17,28 @@ class Singleton {
this.options = options.app.config[this.name] || {};
}

init() {
async init() {
const options = this.options;
assert(!(options.client && options.clients),
`egg:singleton ${this.name} can not set options.client and options.clients both`);

// alias app[name] as client, but still support createInstance method
if (options.client) {
const client = this.createInstance(options.client);
const client = await this.createInstanceAsync(options.client);
this.app[this.name] = client;
assert(!client.createInstance, 'singleton instance should not have createInstance method');
assert(!client.createInstanceAsync, 'singleton instance should not have createInstanceAsync method');
client.createInstance = this.createInstance.bind(this);
client.createInstanceAsync = this.createInstanceAsync.bind(this);
return;
}

// multi clent, use app[name].getInstance(id)
if (options.clients) {
for (const id in options.clients) {
this.clients.set(id, this.createInstance(options.clients[id]));
}
await Promise.all(Object.keys(options.clients).map(id => {
return this.createInstanceAsync(options.clients[id])
.then(client => this.clients.set(id, client));
}));
this.app[this.name] = this;
return;
}
Expand All @@ -48,10 +52,23 @@ class Singleton {
}

createInstance(config) {
// async creator only support createInstanceAsync
assert(!is.asyncFunction(this.create),
`egg:singleton ${this.name} only support create asynchronous, please use createInstanceAsync`);
// options.default will be merge in to options.clients[id]
config = Object.assign({}, this.options.default, config);
return this.create(config, this.app);
}

async createInstanceAsync(config) {
if (typeof config === 'function') {
// support config to be an async function or a normal function
config = await config();
}
// options.default will be merge in to options.clients[id]
config = Object.assign({}, this.options.default, config);
return await this.create(config, this.app);
}
}

module.exports = Singleton;
16 changes: 9 additions & 7 deletions lib/egg.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,15 +384,17 @@ class EggApplication extends EggCore {
/**
* create a singleton instance
* @param {String} name - unique name for singleton
* @param {Object} create - method will be invoked when singleton instance create
* @param {Function|AsyncFunction} create - method will be invoked when singleton instance create
*/
addSingleton(name, create) {
const options = {};
options.name = name;
options.create = create;
options.app = this;
const singleton = new Singleton(options);
singleton.init();
this.beforeStart(async () => {
const options = {};
options.name = name;
options.create = create;
options.app = this;
const singleton = new Singleton(options);
await singleton.init();
});
}

_patchClusterClient(client) {
Expand Down
19 changes: 19 additions & 0 deletions test/app/extend/agent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ describe('test/app/extend/agent.test.js', () => {
const ds = app.agent.dataService.createInstance({ foo: 'barrr' });
config = await ds.getConfig();
assert(config.foo === 'barrr');

const ds2 = await app.agent.dataService.createInstanceAsync({ foo: 'barrr' });
config = await ds2.getConfig();
assert(config.foo === 'barrr');

config = await app.agent.dataServiceAsync.get('second').getConfig();
assert(config.foo === 'bar');
assert(config.foo2 === 'bar2');

try {
app.agent.dataServiceAsync.createInstance({ foo: 'barrr' });
throw new Error('should not execute');
} catch (err) {
assert(err.message === 'egg:singleton dataServiceAsync only support create asynchronous, please use createInstanceAsync');
}

const ds4 = await app.agent.dataServiceAsync.createInstanceAsync({ foo: 'barrr' });
config = await ds4.getConfig();
assert(config.foo === 'barrr');
});
});
});
19 changes: 19 additions & 0 deletions test/app/extend/application.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ describe('test/app/extend/application.test.js', () => {
const ds = app.dataService.createInstance({ foo: 'barrr' });
config = await ds.getConfig();
assert(config.foo === 'barrr');

const ds2 = await app.dataService.createInstanceAsync({ foo: 'barrr' });
config = await ds2.getConfig();
assert(config.foo === 'barrr');

config = await app.dataServiceAsync.get('first').getConfig();
assert(config.foo === 'bar');
assert(config.foo1 === 'bar1');

try {
app.dataServiceAsync.createInstance({ foo: 'barrr' });
throw new Error('should not execute');
} catch (err) {
assert(err.message === 'egg:singleton dataServiceAsync only support create asynchronous, please use createInstanceAsync');
}

const ds4 = await app.dataServiceAsync.createInstanceAsync({ foo: 'barrr' });
config = await ds4.getConfig();
assert(config.foo === 'barrr');
});
});

Expand Down
4 changes: 3 additions & 1 deletion test/fixtures/apps/singleton-demo/agent.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

const createDataService = require('./create');
const createDataService = require('./create').sync;
const createDataServiceAsync = require('./create').async;

module.exports = agent => {
agent.addSingleton('dataService', createDataService);
agent.addSingleton('dataServiceAsync', createDataServiceAsync);
};
4 changes: 3 additions & 1 deletion test/fixtures/apps/singleton-demo/app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

const createDataService = require('./create');
const createDataService = require('./create').sync;
const createDataServiceAsync = require('./create').async;

module.exports = app => {
app.addSingleton('dataService', createDataService);
app.addSingleton('dataServiceAsync', createDataServiceAsync);
};
17 changes: 16 additions & 1 deletion test/fixtures/apps/singleton-demo/config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,22 @@
exports.dataService = {
clients: {
first: { foo1: 'bar1' },
second: { foo2: 'bar2' },
second: async () => {
return { foo2: 'bar2' };
},
},

default: {
foo: 'bar',
}
};

exports.dataServiceAsync = {
clients: {
first: { foo1: 'bar1' },
second: async () => {
return { foo2: 'bar2' };
},
},

default: {
Expand Down
10 changes: 9 additions & 1 deletion test/fixtures/apps/singleton-demo/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ class DataService {
}

let count = 0;
module.exports = function create(config, app) {

exports.sync = (config, app) => {
const done = app.readyCallback(`DataService-${count++}`);
const dataService = new DataService(config);
dataService.ready(done);
return dataService;
};

exports.async = async (config, app) => {
const done = app.readyCallback(`DataService-${count++}`);
const dataService = new DataService(config);
dataService.ready(done);
Expand Down
Loading

0 comments on commit 3a489b6

Please sign in to comment.