Javascript 异步构造函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11856778/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 07:24:48  来源:igfitidea点击:

Asynchronous constructor

javascriptnode.jsasynchronousconstructor

提问by Luke Burns

How can I best handle a situation like the following?

我怎样才能最好地处理以下情况?

I have a constructor that takes a while to complete.

我有一个需要一段时间才能完成的构造函数。

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.

I see three options, each of which seem out of the ordinary.

我看到了三个选项,每个选项都显得与众不同。

One, add a callback to the constructor.

、给构造函数添加回调。

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, use EventEmitter to emit a 'loaded' event.

、使用 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);
});

Or three, block the constructor.

或者三,阻塞构造函数。

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)

But I haven't seen any of this done before.

但我以前从未见过这样做过。

What other options do I have?

我还有什么其他选择?

采纳答案by Jonathan Lonowski

Given the necessity to avoid blocking in Node, the use of events or callbacks isn't so strange(1).

鉴于在 Node 中避免阻塞的必要性,事件或回调的使用并不奇怪(1)

With a slight edit of Two, you could merge it with 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.
}

...

Though, like the fs.readFilein your example, the core Node APIs (at least) often follow the pattern of static functions that expose the instance when the data is ready:

但是,就像fs.readFile您的示例中的那样,核心节点 API(至少)通常遵循静态函数的模式,在数据准备好时公开实例:

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) It shouldn't take very long to read a JSON file. If it is, perhaps a change in storage system is in order for the data.

(1) 读取 JSON 文件应该不会花很长时间。如果是这样,也许存储系统的更改是为了数据。

回答by tim

Update 2: Here is an updated example using an asynchronous factory method. N.B. this requires Node 8 or Babel if run in a browser.

更新 2:这是一个使用异步工厂方法的更新示例。注意,如果在浏览器中运行,这需要 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();

Update: The code below got upvoted a couple of times. However I find this approach using a static method much better: https://stackoverflow.com/a/24686979/2124586

更新:下面的代码得到了多次投票。但是我发现这种使用静态方法的方法要好得多:https: //stackoverflow.com/a/24686979/2124586

ES6 version using promises

使用承诺的 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
});

回答by Shimon Doodkin

I have developed an async constructor:

我开发了一个异步构造函数:

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

(async function() { 
 let s=await new Myclass();
 console.log("s",s)
})();
  • async returns a promise
  • arrow functions pass 'this' as is
  • it is possible to return something else when doing new (you still get a new empty object in this variable. if you call the function without new. you get the original this. like maybe window or global or its holding object).
  • it is possible to return the return value of called async function using await.
  • to use await in normal code, need to wrap the calls with an async anonymous function, that is called instantly. (the called function returns promise and code continues)
  • 异步返回一个承诺
  • 箭头函数按原样传递“this”
  • 在执行 new 时可能会返回其他内容(您仍然会在此变量中获得一个新的空对象。如果您在没有 new 的情况下调用该函数。您将获得原始 this。例如可能是 window 或 global 或其持有对象)。
  • 可以使用 await 返回被调用的异步函数的返回值。
  • 要在普通代码中使用 await,需要使用异步匿名函数包装调用,即立即调用。(被调用的函数返回承诺,代码继续)

my 1st iteration was:

我的第一次迭代是:

maybe just add a callback

也许只是添加一个回调

call an anonymous async function, then call the callback.

调用匿名异步函数,然后调用回调。

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

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

my 2nd iteration was:

我的第二次迭代是:

let's try with a promise instead of a callback. I thought to myself: strange a promise returning a promise, and it worked. .. so the next version is just a promise.

让我们尝试使用承诺而不是回调。我心想:奇怪的是一个承诺返回一个承诺,它奏效了。.. 所以下一个版本只是一个承诺。

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 for old javascript

基于回调的旧 javascript

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

new Myclass(function(s){

});

回答by Will

One thing you could do is preload all the nuclei (maybe inefficient; I don't know how much data it is). The other, which I would recommend if preloading is not an option, would involve a callback with a cache to save loaded nuclei. Here is that approach:

你可以做的一件事是预加载所有的原子核(可能效率低下;我不知道它有多少数据)。另一个,如果预加载不是一种选择,我会推荐它,将涉及带有缓存的回调以保存加载的核。这是这种方法:

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

回答by Simone Sanfratello

This is a bad code design.

这是一个糟糕的代码设计。

The main problem is in the callback your instance it's not still execute the "return", this is what I mean

主要问题是在回调你的实例中它仍然没有执行“返回”,这就是我的意思

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

So, the good code design is to explicitly call the "init" method (or in your case "load_nucleus") after instanced the class

因此,好的代码设计是在实例化类后显式调用“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()
})

回答by Bill Barnes

I extract out the async portions into a fluent method. By convention I call them together.

我将异步部分提取到一个流畅的方法中。按照惯例,我把它们放在一起。

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

I tried a static factory approach wrapping new FooBar(), e.g. FooBar.create(), but it didn't play well with inheritance. If you extend FooBarinto FooBarChild, FooBarChild.create()will still return a FooBar. Whereas with my approach new FooBarChild().create()will return a FooBarChildand it's easy to setup an inheritance chain with create().

我尝试了一种静态工厂方法包装new FooBar(),例如FooBar.create(),但它在继承方面表现不佳。如果扩展FooBarFooBarChildFooBarChild.create()仍会返回一个FooBar。而我的方法new FooBarChild().create()将返回 aFooBarChild并且很容易使用create().

回答by amaksr

You can run constructor function with async functions synchronously via nsynjs. Here is an example to illustrate:

您可以通过nsynjs同步运行带有异步函数的构造函数。下面是一个例子来说明:

index.js (main app logic):

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 (class definition with slow constructor):

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-aware wrapper around slow functions with callbacks):

wrappers.js(nsynjs-aware 围绕带回调的慢函数的包装器):

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;

Full set of files for this example could be found here: https://github.com/amaksr/nsynjs/tree/master/examples/node-async-constructor

可以在此处找到此示例的完整文件集:https: //github.com/amaksr/nsynjs/tree/master/examples/node-async-constructor