Javascript 异步/等待类构造函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/43431550/
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
Async/Await Class Constructor
提问by Alexander Craggs
At the moment, I'm attempting to use async/awaitwithin a class constructor function. This is so that I can get a custom e-mailtag for an Electron project I'm working on.
目前,我正在尝试async/await在类构造函数中使用。这样我就可以获得e-mail我正在处理的 Electron 项目的自定义标签。
customElements.define('e-mail', class extends HTMLElement {
async constructor() {
super()
let uid = this.getAttribute('data-uid')
let message = await grabUID(uid)
const shadowRoot = this.attachShadow({mode: 'open'})
shadowRoot.innerHTML = `
<div id="email">A random email message has appeared. ${message}</div>
`
}
})
At the moment however, the project does not work, with the following error:
然而,目前该项目不起作用,并出现以下错误:
Class constructor may not be an async method
Is there a way to circumvent this so that I can use async/await within this? Instead of requiring callbacks or .then()?
有没有办法绕过这个,以便我可以在其中使用 async/await ?而不是需要回调或 .then()?
回答by slebetman
This can neverwork.
这永远行不通。
The asynckeyword allows awaitto be used in a function marked as asyncbut it also converts that function into a promise generator. So a function marked with asyncwill return a promise. A constructor on the other hand returns the object it is constructing. Thus we have a situation where you want to both return an object and a promise: an impossible situation.
该async关键字允许await在标记为函数中使用async,但它也是功能转换成一个承诺发生器。所以标记为的函数async将返回一个承诺。另一方面,构造函数返回它正在构造的对象。因此,我们有一种情况,您希望同时返回一个对象和一个承诺:一种不可能的情况。
You can only use async/await where you can use promises because they are essentially syntax sugar for promises. You can't use promises in a constructor because a constructor must return the object to be constructed, not a promise.
您只能在可以使用 promise 的地方使用 async/await,因为它们本质上是 promise 的语法糖。不能在构造函数中使用 promise,因为构造函数必须返回要构造的对象,而不是 promise。
There are two design patterns to overcome this, both invented before promises were around.
有两种设计模式可以克服这个问题,它们都是在 promise 出现之前发明的。
Use of an
init()function. This works a bit like jQuery's.ready(). The object you create can only be used inside it's owninitorreadyfunction:Usage:
var myObj = new myClass(); myObj.init(function() { // inside here you can use myObj });Implementation:
class myClass { constructor () { } init (callback) { // do something async and call the callback: callback.bind(this)(); } }Use a builder. I've not seen this used much in javascript but this is one of the more common work-arounds in Java when an object needs to be constructed asynchronously. Of course, the builder pattern is used when constructing an object that requires a lot of complicated parameters. Which is exactly the use-case for asynchronous builders. The difference is that an async builder does not return an object but a promise of that object:
Usage:
myClass.build().then(function(myObj) { // myObj is returned by the promise, // not by the constructor // or builder }); // with async/await: async function foo () { var myObj = await myClass.build(); }Implementation:
class myClass { constructor (async_param) { if (typeof async_param === 'undefined') { throw new Error('Cannot be called directly'); } } static build () { return doSomeAsyncStuff() .then(function(async_result){ return new myClass(async_result); }); } }Implementation with async/await:
class myClass { constructor (async_param) { if (typeof async_param === 'undefined') { throw new Error('Cannot be called directly'); } } static async build () { var async_result = await doSomeAsyncStuff(); return new myClass(async_result); } }
init()函数的使用。这有点像 jQuery 的.ready(). 您创建的对象只能在它自己的init或ready函数中使用:用法:
var myObj = new myClass(); myObj.init(function() { // inside here you can use myObj });执行:
class myClass { constructor () { } init (callback) { // do something async and call the callback: callback.bind(this)(); } }使用构建器。我没有看到这在 javascript 中使用得太多,但是当需要异步构造对象时,这是 Java 中更常见的解决方法之一。当然,在构造需要大量复杂参数的对象时,会使用构建器模式。这正是异步构建器的用例。不同之处在于异步构建器不返回对象而是该对象的承诺:
用法:
myClass.build().then(function(myObj) { // myObj is returned by the promise, // not by the constructor // or builder }); // with async/await: async function foo () { var myObj = await myClass.build(); }执行:
class myClass { constructor (async_param) { if (typeof async_param === 'undefined') { throw new Error('Cannot be called directly'); } } static build () { return doSomeAsyncStuff() .then(function(async_result){ return new myClass(async_result); }); } }使用 async/await 实现:
class myClass { constructor (async_param) { if (typeof async_param === 'undefined') { throw new Error('Cannot be called directly'); } } static async build () { var async_result = await doSomeAsyncStuff(); return new myClass(async_result); } }
Note: although in the examples above we use promises for the async builder they are not strictly speaking necessary. You can just as easily write a builder that accept a callback.
注意:虽然在上面的例子中我们为异步构建器使用了 promise,但严格来说它们并不是必需的。您可以轻松编写接受回调的构建器。
Note on calling functions inside static functions.
注意在静态函数中调用函数。
This has nothing whatsoever to do with async constructors but with what the keyword thisactually mean (which may be a bit surprising to people coming from languages that do auto-resolution of method names, that is, languages that don't need the thiskeyword).
这与异步构造函数没有任何关系,而是与关键字的this实际含义有关(对于来自自动解析方法名称的语言(即不需要this关键字的语言)的人来说,这可能有点令人惊讶)。
The thiskeyword refers to the instantiated object. Not the class. Therefore you cannot normally use thisinside static functions since the static function is not bound to any object but is bound directly to the class.
的this关键字是指实例化的对象。不是班级。因此,您通常不能this在静态函数内部使用,因为静态函数未绑定到任何对象,而是直接绑定到类。
That is to say, in the following code:
也就是说,在下面的代码中:
class A {
static foo () {}
}
You cannot do:
你不能这样做:
var a = new A();
a.foo() // NOPE!!
instead you need to call it as:
相反,您需要将其称为:
A.foo();
Therefore, the following code would result in an error:
因此,以下代码将导致错误:
class A {
static foo () {
this.bar(); // you are calling this as static
// so bar is undefinned
}
bar () {}
}
To fix it you can make bareither a regular function or a static method:
要修复它,您可以创建bar常规函数或静态方法:
function bar1 () {}
class A {
static foo () {
bar1(); // this is OK
A.bar2(); // this is OK
}
static bar2 () {}
}
回答by Downgoat
You candefinitely do this. Basically:
你绝对可以做到这一点。基本上:
class AsyncConstructor {
constructor() {
return (async () => {
// All async code here
this.value = await asyncFunction();
return this; // when done
})();
}
}
to create the class use:
创建类使用:
let instance = await new AsyncConstructor();
This solution has a few short falls though:
不过,这个解决方案有一些不足:
supernote: If you need to usesuper, you cannot call it within the async callback.
super注意:如果您需要使用super,则不能在异步回调中调用它。
TypeScript note:this causes issues with TypeScript because the constructor returns type
Promise<MyClass>instead ofMyClass. There is no definitive way to resolve this that I know of. One potential way suggested by @blitter is to put/** @type {any} */at the beginning of the constructor body— I do not know if this works in all situations however.
TypeScript 注意:这会导致 TypeScript 出现问题,因为构造函数返回类型
Promise<MyClass>而不是MyClass. 据我所知,没有确定的方法来解决这个问题。@blitter 建议的一种潜在方法是放在/** @type {any} */构造函数体的开头——但是我不知道这是否适用于所有情况。
回答by Mike 'Pomax' Kamermans
Based on your comments, you should probably do what every other HTMLElement with asset loading does: make the constructor start a sideloading action, generating a load or error event depending on the result.
根据您的评论,您可能应该执行其他带有资产加载功能的 HTMLElement 所做的事情:使构造函数启动旁加载操作,根据结果生成加载或错误事件。
Yes, that means using promises, but it also means "doing things the same way as every other HTML element", so you're in good company. For instance:
是的,这意味着使用 Promise,但这也意味着“以与其他所有 HTML 元素相同的方式做事”,所以你们相处融洽。例如:
var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";
this kicks off an asynchronous load of the source asset that, when it succeeds, ends in onloadand when it goes wrong, ends in onerror. So, make your own class do this too:
这将启动源资产的异步加载,当它成功onload时,以onerror. 所以,让你自己的班级也这样做:
class EMailElement extends HTMLElement {
constructor() {
super();
this.uid = this.getAttribute('data-uid');
}
setAttribute(name, value) {
super.setAttribute(name, value);
if (name === 'data-uid') {
this.uid = value;
}
}
set uid(input) {
if (!input) return;
const uid = parseInt(input);
// don't fight the river, go with the flow
let getEmail = new Promise( (resolve, reject) => {
yourDataBase.getByUID(uid, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
// kick off the promise, which will be async all on its own
getEmail()
.then(result => {
this.renderLoaded(result.message);
})
.catch(error => {
this.renderError(error);
});
}
};
customElements.define('e-mail', EmailElement);
And then you make the renderLoaded/renderError functions deal with the event calls and shadow dom:
然后让 renderLoaded/renderError 函数处理事件调用和 shadow dom:
renderLoaded(message) {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<div class="email">A random email message has appeared. ${message}</div>
`;
// is there an ancient event listener?
if (this.onload) {
this.onload(...);
}
// there might be modern event listeners. dispatch an event.
this.dispatchEvent(new Event('load', ...));
}
renderFailed() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<div class="email">No email messages.</div>
`;
// is there an ancient event listener?
if (this.onload) {
this.onerror(...);
}
// there might be modern event listeners. dispatch an event.
this.dispatchEvent(new Event('error', ...));
}
Also note I changed your idto a class, because unless you write some weird code to only ever allow a single instance of your <e-mail>element on a page, you can't use a unique identifier and then assign it to a bunch of elements.
另请注意,我将您的更改id为 a class,因为除非您编写一些奇怪的代码以仅允许<e-mail>页面上元素的单个实例,否则您不能使用唯一标识符然后将其分配给一堆元素。
回答by Vidar
Because async functions are promises, you can create a static function on your class which executes an async function which returns the instance of the class:
因为异步函数是承诺,所以你可以在你的类上创建一个静态函数,它执行一个返回类实例的异步函数:
class Yql {
constructor () {
// Set up your class
}
static init () {
return (async function () {
let yql = new Yql()
// Do async stuff
await yql.build()
// Return instance
return yql
}())
}
async build () {
// Do stuff with await if needed
}
}
async function yql () {
// Do this instead of "new Yql()"
let yql = await Yql.init()
// Do stuff with yql instance
}
yql()
Call with let yql = await Yql.init()from an async function.
let yql = await Yql.init()从异步函数调用 with 。
回答by ninjagecko
The stopgap solution
权宜之计
You can create an async init() {... return this;}method, then instead do new MyClass().init()whenever you'd normally just say new MyClass().
您可以创建一个async init() {... return this;}方法,然后new MyClass().init()在您通常只说new MyClass().
This is not clean because it relies on everyone who uses your code, and yourself, to always instantiate the object like so. However if you're only using this object in a particular place or two in your code, it could maybe be fine.
这并不干净,因为它依赖于使用您的代码的每个人以及您自己,总是像这样实例化对象。但是,如果您仅在代码中的一两个特定位置使用此对象,则可能没问题。
A significant problem though occurs because ES has no type system, so if you forget to call it, you've just returned undefinedbecause the constructor returns nothing. Oops. Much better would be to do something like:
一个严重的问题发生了,因为 ES 没有类型系统,所以如果你忘记调用它,你只是返回,undefined因为构造函数什么都不返回。哎呀。更好的是做这样的事情:
The best thing to do would be:
最好的做法是:
class AsyncOnlyObject {
constructor() {
}
async init() {
this.someField = await this.calculateStuff();
}
async calculateStuff() {
return 5;
}
}
async function newAsync_AsyncOnlyObject() {
return await new AsyncOnlyObject().init();
}
newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject?{someField: 5}
The factory method solution (slightly better)
工厂方法解决方案(稍微好一点)
However then you might accidentally do new AsyncOnlyObject, you should probably just create factory function that uses Object.create(AsyncOnlyObject.prototype)directly:
但是,您可能会不小心执行新的 AsyncOnlyObject,您可能应该创建Object.create(AsyncOnlyObject.prototype)直接使用的工厂函数:
async function newAsync_AsyncOnlyObject() {
return await Object.create(AsyncOnlyObject.prototype).init();
}
newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject?{someField: 5}
However say you want to use this pattern on many objects... you could abstract this as a decorator or something you (verbosely, ugh) call after defining like postProcess_makeAsyncInit(AsyncOnlyObject), but here I'm going to use extendsbecause it sort of fits into subclass semantics (subclasses are parent class + extra, in that they should obey the design contract of the parent class, and may do additional things; an async subclass would be strange if the parent wasn't also async, because it could not be initialized the same way):
但是,如果您想在许多对象上使用此模式……您可以将其抽象为装饰器或您在定义 like 后(详细地,呃)调用的东西postProcess_makeAsyncInit(AsyncOnlyObject),但在这里我将使用extends它,因为它有点适合子类语义(子类是父类+额外的,因为它们应该遵守父类的设计契约,并且可能会做额外的事情;如果父类不是异步的,异步子类会很奇怪,因为它不能被初始化为相同的道路):
Abstracted solution (extends/subclass version)
抽象解决方案(扩展/子类版本)
class AsyncObject {
constructor() {
throw new Error('classes descended from AsyncObject must be initialized as (await) TheClassName.anew(), rather than new TheClassName()');
}
static async anew(...args) {
var R = Object.create(this.prototype);
R.init(...args);
return R;
}
}
class MyObject extends AsyncObject {
async init(x, y=5) {
this.x = x;
this.y = y;
// bonus: we need not return 'this'
}
}
MyObject.anew('x').then(console.log);
// output: MyObject?{x: "x", y: 5}
(do not use in production: I have not thought through complicated scenarios such as whether this is the proper way to write a wrapper for keyword arguments.)
(不要在生产中使用:我没有考虑过复杂的场景,例如这是否是为关键字参数编写包装器的正确方法。)
回答by Davide Cannizzo
Unlike others have said, you can get it to work.
与其他人所说的不同,您可以让它发挥作用。
JavaScript classes can return literally anything from their constructor, even an instance of another class. So, you might return a Promisefrom the constructor of your class that resolves to its actual instance.
JavaScript classes 可以从它们的 返回任何东西constructor,甚至是另一个类的实例。因此,您可能会Promise从解析为其实际实例的类的构造函数中返回 a 。
Below is an example:
下面是一个例子:
export class Foo {
constructor() {
return (async () => {
// await anything you want
return this; // Return the newly-created instance
}).call(this);
}
}
Then, you'll create instances of Foothis way:
然后,您将以Foo这种方式创建实例:
const foo = await new Foo();
回答by Juan Lanus
I made this test-case based on @Downgoat's answer.
It runs on NodeJS.
This is Downgoat's code where the async part is provided by a setTimeout()call.
我根据@Downgoat 的回答制作了这个测试用例。
它在 NodeJS 上运行。这是 Downgoat 的代码,其中异步部分由setTimeout()调用提供。
'use strict';
const util = require( 'util' );
class AsyncConstructor{
constructor( lapse ){
this.qqq = 'QQQ';
this.lapse = lapse;
return ( async ( lapse ) => {
await this.delay( lapse );
return this;
})( lapse );
}
async delay(ms) {
return await new Promise(resolve => setTimeout(resolve, ms));
}
}
let run = async ( millis ) => {
// Instatiate with await, inside an async function
let asyncConstructed = await new AsyncConstructor( millis );
console.log( 'AsyncConstructor: ' + util.inspect( asyncConstructed ));
};
run( 777 );
My use case is DAOs for the server-side of a web application.
As I see DAOs, they are each one associated to a record format, in my case a MongoDB collection like for instance a cook.
A cooksDAO instance holds a cook's data.
In my restless mind I would be able to instantiate a cook's DAO providing the cookId as an argument, and the instantiation would create the object and populate it with the cook's data.
Thus the need to run async stuff into the constructor.
I wanted to write:
我的用例是用于 Web 应用程序服务器端的 DAO。
正如我所看到的 DAO,它们每个都与一种记录格式相关联,在我的例子中是一个 MongoDB 集合,例如一个厨师。
CooksDAO 实例保存了厨师的数据。
在我焦躁不安的脑海中,我将能够实例化一个厨师的 DAO,提供 CookId 作为参数,并且实例化将创建对象并用厨师的数据填充它。
因此需要在构造函数中运行异步内容。
我想写:
let cook = new cooksDAO( '12345' );
to have available properties like cook.getDisplayName().
With this solution I have to do:
拥有可用的属性,如cook.getDisplayName().
有了这个解决方案,我必须这样做:
let cook = await new cooksDAO( '12345' );
which is very similar to the ideal.
Also, I need to do this inside an asyncfunction.
这与理想情况非常相似。
另外,我需要在async函数内部执行此操作。
My B-plan was to leave the data loading out of the constructor, based on @slebetman suggestion to use an init function, and do something like this:
我的 B 计划是根据 @slebetman 建议使用 init 函数,将数据从构造函数中加载出来,并执行以下操作:
let cook = new cooksDAO( '12345' );
async cook.getData();
which doesn't break the rules.
这不违反规则。
回答by Aliaksandr Shpak
use async method in construct???
在构造中使用异步方法???
constructor(props) {
super(props);
(async () => await this.qwe(() => console.log(props), () => console.log(props)))();
}
async qwe(q, w) {
return new Promise((rs, rj) => {
rs(q());
rj(w());
});
}
回答by 7ynk3r
If you can avoidextend, you can avoid classes all together and use function composition as constructors. You can use the variables in the scope instead of class members:
如果可以避免extend,则可以避免一起使用类并使用函数组合作为构造函数。您可以使用作用域中的变量而不是类成员:
async function buildA(...) {
const data = await fetch(...);
return {
getData: function() {
return data;
}
}
}
and simple use it as
并简单地将其用作
const a = await buildA(...);
If you're using typescript or flow, you can even enforce the interface of the constructors
如果您使用的是 typescript 或 flow,您甚至可以强制执行构造函数的接口
Interface A {
getData: object;
}
async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...
回答by Umesh KC
You may immediately invoke an anonymous async function that returns message and set it to the message variable. You might want to take a look at immediately invoked function expressions (IEFES), in case you are unfamiliar with this pattern. This will work like a charm.
您可以立即调用返回消息的匿名异步函数并将其设置为消息变量。如果您不熟悉此模式,您可能需要查看立即调用函数表达式 (IEFES)。这将像魅力一样发挥作用。
var message = (async function() { return await grabUID(uid) })()

