Асинхронный конструктор
Как лучше всего справиться с ситуацией, подобной следующей?
У меня есть конструктор, для завершения которого требуется некоторое время.
var Element = function Element(name){
this.name = name;
this.nucleus = {};
this.load_nucleus(name); // This might take a second.
}
var oxygen = new Element('oxygen');
console.log(oxygen.nucleus); // Returns {}, because load_nucleus hasn't finished.
Я вижу три варианта, каждый из которых кажется обычным.
Один, добавьте обратный вызов конструктору.
var Element = function Element(name, fn){
this.name = name;
this.nucleus = {};
this.load_nucleus(name, function(){
fn(); // Now continue.
});
}
Element.prototype.load_nucleus(name, fn){
fs.readFile(name+'.json', function(err, data) {
this.nucleus = JSON.parse(data);
fn();
});
}
var oxygen = new Element('oxygen', function(){
console.log(oxygen.nucleus);
});
Два, используйте EventEmitter для генерации "загруженного" события.
var Element = function Element(name){
this.name = name;
this.nucleus = {};
this.load_nucleus(name); // This might take a second.
}
Element.prototype.load_nucleus(name){
var self = this;
fs.readFile(name+'.json', function(err, data) {
self.nucleus = JSON.parse(data);
self.emit('loaded');
});
}
util.inherits(Element, events.EventEmitter);
var oxygen = new Element('oxygen');
oxygen.once('loaded', function(){
console.log(this.nucleus);
});
Или три, заблокируйте конструктор.
var Element = function Element(name){
this.name = name;
this.nucleus = {};
this.load_nucleus(name); // This might take a second.
}
Element.prototype.load_nucleus(name, fn){
this.nucleus = JSON.parse(fs.readFileSync(name+'.json'));
}
var oxygen = new Element('oxygen');
console.log(oxygen.nucleus)
Но я не видел ничего подобного раньше.
Какие еще параметры у меня есть?
Ответы
Ответ 1
Учитывая необходимость избегать блокировки в Node, использование событий или обратных вызовов не так странно (1).
С небольшим редактированием Two вы можете объединить его с One:
var Element = function Element(name, fn){
this.name = name;
this.nucleus = {};
if (fn) this.on('loaded', fn);
this.load_nucleus(name); // This might take a second.
}
...
Хотя, как и fs.readFile
в вашем примере, основные API-интерфейсы Node (по крайней мере) часто следуют шаблону статических функций, которые выставляют экземпляр, когда данные готовы:
var Element = function Element(name, nucleus) {
this.name = name;
this.nucleus = nucleus;
};
Element.create = function (name, fn) {
fs.readFile(name+'.json', function(err, data) {
var nucleus = err ? null : JSON.parse(data);
fn(err, new Element(name, nucleus));
});
};
Element.create('oxygen', function (err, elem) {
if (!err) {
console.log(elem.name, elem.nucleus);
}
});
(1) Не нужно долго читать JSON файл. Если это так, возможно, изменение в системе хранения для данных.
Ответ 2
Обновление 2:
Ниже приведен пример с использованием асинхронного метода factory. Нотабене для этого требуется Node 8 или Babel, если они запускаются в браузере.
class Element {
constructor(nucleus){
this.nucleus = nucleus;
}
static async createElement(){
const nucleus = await this.loadNucleus();
return new Element(nucleus);
}
static async loadNucleus(){
// do something async here and return it
return 10;
}
}
async function main(){
const element = await Element.createElement();
// use your element
}
main();
Обновление:
Приведенный ниже код несколько раз поддерживался. Однако я нахожу этот подход, используя статический метод намного лучше:
fooobar.com/questions/57876/...
Версия ES6 с использованием promises
class Element{
constructor(){
this.some_property = 5;
this.nucleus;
return new Promise((resolve) => {
this.load_nucleus().then((nucleus) => {
this.nucleus = nucleus;
resolve(this);
});
});
}
load_nucleus(){
return new Promise((resolve) => {
setTimeout(() => resolve(10), 1000)
});
}
}
//Usage
new Element().then(function(instance){
// do stuff with your instance
});
Ответ 3
Одна вещь, которую вы могли бы сделать, - предварительно загрузить все ядра (возможно, неэффективно, я не знаю, сколько данных это). Другой, который я бы рекомендовал, если предварительная загрузка не является вариантом, будет включать обратный вызов с кешем для сохранения загруженных ядер. Вот такой подход:
Element.nuclei = {};
Element.prototype.load_nucleus = function(name, fn){
if ( name in Element.nuclei ) {
this.nucleus = Element.nuclei[name];
return fn();
}
fs.readFile(name+'.json', function(err, data) {
this.nucleus = Element.nuclei[name] = JSON.parse(data);
fn();
});
}
Ответ 4
Это плохой дизайн кода.
Основная проблема заключается в обратном вызове вашего экземпляра, который он еще не выполняет "возврат", это то, что я имею в виду
var MyClass = function(cb) {
doAsync(function(err) {
cb(err)
}
return {
method1: function() { },
method2: function() { }
}
}
var _my = new MyClass(function(err) {
console.log('instance', _my) // < _my is still undefined
// _my.method1() can't run any methods from _my instance
})
_my.method1() // < it run the function, but it not yet inited
Таким образом, хорошая конструкция кода заключается в явном вызове метода init (или в вашем случае "load_nucleus" ) после того, как был введен класс
var MyClass = function() {
return {
init: function(cb) {
doAsync(function(err) {
cb(err)
}
},
method1: function() { },
method2: function() { }
}
}
var _my = new MyClass()
_my.init(function(err) {
if(err) {
console.error('init error', err)
return
}
console.log('inited')
// _my.method1()
})
Ответ 5
Я разработал конструктор async:
function Myclass(){
return (async () => {
... code here ...
return this;
})();
}
(async function() {
let s=await new Myclass();
console.log("s",s)
})();
- async возвращает обещание
- функции стрелок проходят 'this' как есть
- можно вернуть значение в результате ожидания, как возврат вызванной функции async.
- при выполнении нового можно вернуть что-то еще.
- чтобы использовать ожидание в обычном коде, необходимо обернуть вызовы анонимной функцией асинхронного вызова, которая называется мгновенно. (вызываемая функция возвращает обещание и код продолжается)
моя первая итерация была:
возможно просто добавить обратный вызов
вызов анонимной асинхронной функции,
затем вызовите обратный вызов.
function Myclass(cb){
var asynccode=(async () => {
await this.something1();
console.log(this.result)
})();
if(cb)
asynccode.then(cb.bind(this))
}
моя вторая итерация была:
попробуйте с обещанием вместо обратного вызова.
Я подумал про себя: странное обещание вернуло обещание, и это сработало... поэтому следующая версия - это просто обещание.
function Myclass(){
this.result=false;
var asynccode=(async () => {
await new Promise (resolve => setTimeout (()=>{this.result="ok";resolve()}, 1000))
console.log(this.result)
return this;
})();
return asynccode;
}
(async function() {
let s=await new Myclass();
console.log("s",s)
})();
callback-based для старого javascript
function Myclass(cb){
var that=this;
var cb_wrap=function(data){that.data=data;cb(that)}
getdata(cb_wrap)
}
new Myclass(function(s){
});
Ответ 6
Вы можете запускать функцию конструктора с асинхронными функциями синхронно через nsynjs. Вот пример, иллюстрирующий:
index.js(основная логика приложения):
var nsynjs = require('nsynjs');
var modules = {
MyObject: require('./MyObject')
};
function synchronousApp(modules) {
try {
var myObjectInstance1 = new modules.MyObject('data1.json');
var myObjectInstance2 = new modules.MyObject('data2.json');
console.log(myObjectInstance1.getData());
console.log(myObjectInstance2.getData());
}
catch (e) {
console.log("Error",e);
}
}
nsynjs.run(synchronousApp,null,modules,function () {
console.log('done');
});
MyObject.js(определение класса с медленным конструктором):
var nsynjs = require('nsynjs');
var synchronousCode = function (wrappers) {
var config;
// constructor of MyObject
var MyObject = function(fileName) {
this.data = JSON.parse(wrappers.readFile(nsynjsCtx, fileName).data);
};
MyObject.prototype.getData = function () {
return this.data;
};
return MyObject;
};
var wrappers = require('./wrappers');
nsynjs.run(synchronousCode,{},wrappers,function (m) {
module.exports = m;
});
wrappers.js(обертка, поддерживающая nsynjs, вокруг медленных функций с обратными вызовами):
var fs=require('fs');
exports.readFile = function (ctx,name) {
var res={};
fs.readFile( name, "utf8", function( error , configText ){
if( error ) res.error = error;
res.data = configText;
ctx.resume(error);
} );
return res;
};
exports.readFile.nsynjsHasCallback = true;
Полный набор файлов для этого примера можно найти здесь: https://github.com/amaksr/nsynjs/tree/master/examples/node-async-constructor