Подтвердить что ты не робот

Асинхронный конструктор

Как лучше всего справиться с ситуацией, подобной следующей?

У меня есть конструктор, для завершения которого требуется некоторое время.

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)

Но я не видел ничего подобного раньше.

Какие еще параметры у меня есть?

4b9b3361

Ответ 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