web-dev-qa-db-ja.com

非同期コンストラクター

次のような状況に最適に対処するにはどうすればよいですか?

完了するまでに時間がかかるコンストラクターがあります。

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.

3つのオプションがありますが、それぞれが普通ではないように見えます。

One、コールバックをコンストラクタに追加します。

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);
});

Two、EventEmitterを使用して 'loaded'イベントを発行します。

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)

しかし、私はこれのいずれも以前に見たことがありません。

他にどんなオプションがありますか?

51
Luke Burns

更新2:これは、非同期ファクトリーメソッドを使用した更新された例です。 N.B.ブラウザで実行する場合は、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();

更新:以下のコードは数回支持されました。ただし、静的メソッドを使用したこのアプローチははるかに優れています: https://stackoverflow.com/a/24686979/2124586

Promiseを使用したES6バージョン

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
});
25
tim

できることの1つは、すべてのニュークリアスをプリロードすることです(非効率かもしれません。どのくらいのデータかはわかりません)。もう1つは、プリロードがオプションではない場合にお勧めしますが、ロードされたニュークリアスを保存するためのキャッシュを備えたコールバックが含まれます。そのアプローチは次のとおりです。

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();
   });
}
3
Will

私は非同期コンストラクタを開発しました:

function Myclass(){
 return (async () => {
     ... code here ...
     return this;
 })();
}

(async function() { 
 let s=await new Myclass();
 console.log("s",s)
})();
  • asyncはプロミスを返します
  • 矢印関数は「this」をそのまま渡します
  • 新規作成時に他の何かを返すことができます(この変数で新しい空のオブジェクトを取得します。newなしで関数を呼び出すと、元のthisを取得します。ウィンドウまたはグローバルまたはその保持オブジェクトなど)。
  • awaitを使用して、呼び出された非同期関数の戻り値を返すことができます。
  • 通常のコードでawaitを使用するには、すぐに呼び出される非同期の匿名関数で呼び出しをラップする必要があります。 (呼び出された関数はpromiseを返し、コードは続行します)

私の最初の反復は:

たぶんコールバックを追加するだけ

匿名の非同期関数を呼び出してから、コールバックを呼び出します。

function Myclass(cb){
 var asynccode=(async () => { 
     await this.something1();
     console.log(this.result)
 })();

 if(cb)
    asynccode.then(cb.bind(this))
}

私の2回目の繰り返しは:

コールバックの代わりにプロミスを試してみましょう。私は自分自身に考えました:奇妙な約束が約束を返し、それはうまくいきました。 ..そのため、次のバージョンは単なる約束です。

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)
})();

古いjavascriptのコールバックベース

function Myclass(cb){
 var that=this;
 var cb_wrap=function(data){that.data=data;cb(that)}
 getdata(cb_wrap)
}

new Myclass(function(s){

});
3
Shimon Doodkin

これは悪いコード設計です。

主な問題は、あなたのインスタンスのコールバックで、それがまだ「リターン」を実行しないことです、これは私が意味することです

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's 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()
})
2

非同期部分を流extractなメソッドに抽出します。慣例により、私はそれらを一緒に呼びます。

_class FooBar {
  constructor() {
    this.foo = "foo";
  }
  
  async create() {
    this.bar = await bar();

    return this;
  }
}

async function bar() {
  return "bar";
}

async function main() {
  const foobar = await new FooBar().create(); // two-part constructor
  console.log(foobar.foo, foobar.bar);
}

main(); // foo bar_

new FooBar()をラッピングする静的なファクトリアプローチを試みました。 FooBar.create()が、継承ではうまく機能しませんでした。 FooBarFooBarChildに拡張しても、FooBarChild.create()FooBarを返します。一方、私のアプローチではnew FooBarChild().create()FooBarChildを返し、create()を使用して継承チェーンを簡単にセットアップできます。

0
Bill Barnes