TypeScript 中的异步构造函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35743426/
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 constructor functions in TypeScript?
提问by dcsan
I have some setup I want during a constructor, but it seems that is not allowed
我在构造函数期间有一些我想要的设置,但似乎不允许
Which means I can't use:
这意味着我不能使用:
How else should I do this?
我还应该怎么做?
Currently I have something outside like this, but this is not guaranteed to run in the order I want?
目前我在外面有这样的东西,但这不能保证按我想要的顺序运行?
async function run() {
let topic;
debug("new TopicsModel");
try {
topic = new TopicsModel();
} catch (err) {
debug("err", err);
}
await topic.setup();
采纳答案by Amid
Constructor must return instance of the class it 'constructs' therefore its not possible to return Promise<...> and await for it.
构造函数必须返回它“构造”的类的实例,因此不可能返回 Promise<...> 并等待它。
You can:
你可以:
- Make your public setup async
- Do not call it from constructor.
Call it whenever you want to 'finalize' object construction
async function run() { let topic; debug("new TopicsModel"); try { topic = new TopicsModel(); await topic.setup(); } catch (err) { debug("err", err); } }
- 使您的公共设置异步
- 不要从构造函数调用它。
每当您想“完成”对象构造时调用它
async function run() { let topic; debug("new TopicsModel"); try { topic = new TopicsModel(); await topic.setup(); } catch (err) { debug("err", err); } }
回答by Peter Wone
Readiness design pattern
就绪设计模式
If you can't put the object in a promise, put a promise in the object.
如果不能将对象放入承诺中,请在对象中放入承诺。
The problem is more tractable when correctly framed. The objective is not to wait on construction but to wait on readinessof the constructed object. These are two completely different things. It is even possible for something like a database connection object to be in a ready state, go back to a non-ready state, then become ready again.
当正确构架时,这个问题更容易处理。目标不是等待构造,而是等待构造对象的准备就绪。这是完全不同的两件事。甚至有可能像数据库连接对象这样的东西处于就绪状态,回到非就绪状态,然后再次变为就绪状态。
How can we determine readiness if it depends on activities that may not be complete when the constructor returns? Quite obviously readiness is a property of the object. Many frameworks directly express the notion of readiness. In JavaScript we have the Promise
, and in C# we have the Task
. Both have direct language support for object properties.
如果它取决于构造函数返回时可能未完成的活动,我们如何确定准备就绪?很明显,就绪是对象的一个属性。许多框架直接表达了就绪的概念。在 JavaScript 中Promise
,我们有Task
. 两者都有对对象属性的直接语言支持。
Expose the construction completion promise as a property of the constructed object. When the asynchronous part of your construction finishes it should resolve the promise.
将构造完成承诺公开为构造对象的属性。当您的构造的异步部分完成时,它应该解决承诺。
It doesn't matter whether .then(...)
executes before or after the promise resolves. The promise specification states that invoking then
on an already resolved promised simply executes the handler immediately.
.then(...)
在 promise 解决之前或之后执行都没有关系。promise 规范指出,调用then
已解决的 promise只会立即执行处理程序。
class Foo {
public Ready: Promise.IThenable<any>;
constructor() {
...
this.Ready = new Promise((resolve, reject) => {
$.ajax(...).then(result => {
// use result
resolve(undefined);
}).fail(reject);
});
}
}
var foo = new Foo();
foo.Ready.then(() => {
//do stuff that needs foo to be ready, eg apply bindings
});
Why resolve(undefined);
instead of resolve();
? Because ES6. Adjust as required to suit your target.
为什么resolve(undefined);
而不是resolve();
?因为 ES6。根据需要进行调整以适合您的目标。
In a comment it has been suggested that I should have framed this solution with await
to more directly address the question as asked.
在评论中,有人建议我应该制定这个解决方案,await
以便更直接地解决所提出的问题。
This is a poor solution because it permits only the code in the scope immediately following the await statement to wait on completion. Exposing a promise object as a property of an asynchronously initialised object means any code anywhere can guarantee that initialisation is complete because the promise is in scope everywhere the object is in scope, so it is guaranteed available everywhere that the risk exists.
这是一个糟糕的解决方案,因为它只允许紧跟在 await 语句之后的范围内的代码等待完成。将承诺对象作为异步初始化对象的属性公开意味着任何地方的任何代码都可以保证初始化完成,因为承诺在对象范围内的任何地方都在范围内,因此保证在存在风险的任何地方都可用。
Besides, it is unlikely that using the await keyword is a deliverable for any project that isn't a university assignment demonstrating use of the await keyword.
此外,对于不是展示使用 await 关键字的大学作业的任何项目,使用 await 关键字不太可能是可交付成果。
This is original work by me. I devised this design pattern because I was unsatisfied with external factories and other such workarounds. Despite searching for some time, I found no prior art for my solution, so I'm claiming credit as the originator of this pattern until disputed.
这是我的原创作品。我设计这种设计模式是因为我对外部工厂和其他此类解决方法不满意。尽管搜索了一段时间,但我没有找到我的解决方案的现有技术,所以我声称是这种模式的创始人,直到有争议。
In 2020 I discovered that in 2013 Stephen Cleary posted a verysimilar solution to the problem. Looking back through my own work the first vestiges of this approach appear in code I worked on around the same time. I suspect Cleary put it all together first but he didn't formalise it as a design pattern or publish it where it would be easily found by others with the problem. Moreover, Cleary deals only with construction which is only one application of the Readiness pattern (see below).
2020 年,我发现 2013 年 Stephen Cleary 发布了一个非常相似的问题解决方案。回顾我自己的工作,这种方法的第一个痕迹出现在我几乎同时处理的代码中。我怀疑Cleary 是先把它们放在一起,但他并没有将其正式化为一种设计模式,也没有将其发布到其他有问题的人很容易找到的地方。此外,Cleary 只处理构建,这只是就绪模式的一种应用(见下文)。
In a comment, @suhas suggests the use of await
rather than .then
and this would work but it's less broadly compatible. On the matter of compatibility, Typescript has changed since I wrote this, and now you would have to declare public Ready: Promise<any>
在评论中,@suhas 建议使用await
而不是.then
,这会起作用,但不那么广泛兼容。关于兼容性问题,自从我写这篇文章以来,Typescript 发生了变化,现在你必须声明public Ready: Promise<any>
Some objects have methods that temporarily put them in an invalid condition, and the pattern can serve in that scenario without modification. Code of the form obj.Ready.then(...)
will always use whatever promise property is returned by the Ready
property, so whenever some action is about to invalidate object state, a fresh promise can be created.
某些对象具有暂时将它们置于无效状态的方法,并且该模式无需修改即可在该场景中使用。表单的代码obj.Ready.then(...)
将始终使用该Ready
属性返回的任何承诺属性,因此每当某些操作即将使对象状态无效时,就可以创建一个新的承诺。
回答by Dave Cousineau
Use an asynchronous factory method instead.
请改用异步工厂方法。
class MyClass {
private mMember: Something;
constructor() {
this.mMember = await SomeFunctionAsync(); // error
}
}
Becomes:
变成:
class MyClass {
private mMember: Something;
// make private if possible; I can't in TS 1.8
constructor() {
}
public static CreateAsync = async () => {
const me = new MyClass();
me.mMember = await SomeFunctionAsync();
return me;
};
}
This will mean that you will have to await the construction of these kinds of objects, but that should already be implied by the fact that you are in the situation where you have to await something to construct them anyway.
这意味着您将不得不等待构建这些类型的对象,但这应该已经暗示了,因为您处于无论如何都必须等待某些东西来构建它们的情况。
There's another thing you can do but I suspect it's not a good idea:
您还可以做另一件事,但我怀疑这不是一个好主意:
// probably BAD
class MyClass {
private mMember: Something;
constructor() {
this.LoadAsync();
}
private LoadAsync = async () => {
this.mMember = await SomeFunctionAsync();
};
}
This can work and I've never had an actual problem from it before, but it seems to be dangerous to me, since your object will not actually be fully initialized when you start using it.
这可以工作,我以前从未遇到过实际问题,但这对我来说似乎很危险,因为当您开始使用它时,您的对象实际上不会完全初始化。
回答by Fis
I know its quiet old but another option is to have a factory which will create the object and wait for its initialization:
我知道它很安静,但另一种选择是拥有一个工厂来创建对象并等待其初始化:
// Declare the class
class A {
// Declare class constructor
constructor() {
// We didn't finish the async job yet
this.initialized = false;
// Simulates async job, it takes 5 seconds to have it done
setTimeout(() => {
this.initialized = true;
}, 5000);
}
// do something usefull here - thats a normal method
usefull() {
// but only if initialization was OK
if (this.initialized) {
console.log("I am doing something usefull here")
// otherwise throw error which will be catched by the promise catch
} else {
throw new Error("I am not initialized!");
}
}
}
// factory for common, extensible class - thats the reason of the constructor parameter
// it can be more sophisticated and accept also params for constructor and pass them there
// also, the timeout is just example, it will wait about 10s (1000 x 10ms iterations
function factory(construct) {
// create a promise
var aPromise = new Promise(
function(resolve, reject) {
// construct the object here
var a = new construct();
// setup simple timeout
var timeout = 1000;
// called in 10ms intervals to check if the object is initialized
function waiter() {
if (a.initialized) {
// if initialized, resolve the promise
resolve(a);
} else {
// check for timeout - do another iteration after 10ms or throw exception
if (timeout > 0) {
timeout--;
setTimeout(waiter, 10);
} else {
throw new Error("Timeout!");
}
}
}
// call the waiter, it will return almost immediately
waiter();
}
);
// return promise of object being created and initialized
return aPromise;
}
// this is some async function to create object of A class and do something with it
async function createObjectAndDoSomethingUsefull() {
// try/catch to capture exceptions during async execution
try {
// create object and wait until its initialized (promise resolved)
var a = await factory(A);
// then do something usefull
a.usefull();
} catch(e) {
// if class instantiation failed from whatever reason, timeout occured or usefull was called before the object finished its initialization
console.error(e);
}
}
// now, perform the action we want
createObjectAndDoSomethingUsefull();
// spagetti code is done here, but async probably still runs
回答by Paul Flame
I've found a solution that looks like
我找到了一个看起来像的解决方案
export class SomeClass {
private initialization;
// Implement async constructor
constructor() {
this.initialization = this.init();
}
async init() {
await someAsyncCall();
}
async fooMethod() {
await this.initialization();
// ...some other stuff
}
async barMethod() {
await this.initialization();
// ...some other stuff
}
It works because Promises that powers async/await, can be resolved multiple times with the same value.
它之所以有效,是因为支持 async/await 的 Promise 可以使用相同的值多次解析。
回答by Abakhan
Use a setup async method that returns the instance
使用返回实例的设置异步方法
I had a similar problem in the following case: how to instanciate a 'Foo' class either with an instance of a 'FooSession' class or with a 'fooSessionParams' object, knowing that creating a fooSession from a fooSessionParams object is an async function? I wanted to instanciate either by doing:
我在以下情况下遇到了类似的问题:如何使用“FooSession”类的实例或“fooSessionParams”对象实例化“Foo”类,知道从 fooSessionParams 对象创建 fooSession 是一个异步函数?我想通过以下方式实例化:
let foo = new Foo(fooSession);
or
或者
let foo = await new Foo(fooSessionParams);
and did'nt want a factory because the two usages would have been too different. But as we know, we can not return a promise from a constructor (and the return signature is different). I solved it this way:
并且不想要一个工厂,因为这两种用法差别太大了。但是我们知道,我们不能从构造函数返回承诺(并且返回签名是不同的)。我是这样解决的:
class Foo {
private fooSession: FooSession;
constructor(fooSession?: FooSession) {
if (fooSession) {
this.fooSession = fooSession;
}
}
async setup(fooSessionParams: FooSessionParams): Promise<Foo> {
this.fooSession = await getAFooSession(fooSessionParams);
return this;
}
}
The interesting part is where the setup async method returns the instance itself. Then if I have a 'FooSession' instance I can use it this way:
有趣的部分是 setup async 方法返回实例本身的地方。然后,如果我有一个 'FooSession' 实例,我可以这样使用它:
let foo = new Foo(fooSession);
And if I have no 'FooSession' instance I can setup 'foo' in one of these ways:
如果我没有 'FooSession' 实例,我可以通过以下方式之一设置 'foo':
let foo = await new Foo().setup(fooSessionParams);
(witch is my prefered way because it is close to what I wanted first) or
(女巫是我的首选方式,因为它接近我首先想要的)或
let foo = new Foo();
await foo.setup(fooSessionParams);
As an alternative I could also add the static method:
作为替代方案,我还可以添加静态方法:
static async getASession(fooSessionParams: FooSessionParams): FooSession {
let fooSession: FooSession = await getAFooSession(fooSessionParams);
return fooSession;
}
and instanciate this way:
并以这种方式实例化:
let foo = new Foo(await Foo.getASession(fooSessionParams));
It is mainly a question of style…
主要是风格问题……
回答by Jim
You may elect to leave the await outof the equation altogether. You can call it from the constructor if you need to. The caveat being that you need to deal with any return values in the setup/initialise function, not in the constructor.
您可以选择将 await完全排除在等式之外。如果需要,您可以从构造函数调用它。需要注意的是,您需要在 setup/initialise 函数中而不是在构造函数中处理任何返回值。
this works for me, using angular 1.6.3.
这对我有用,使用 angular 1.6.3。
import { module } from "angular";
import * as R from "ramda";
import cs = require("./checkListService");
export class CheckListController {
static $inject = ["$log", "$location", "ICheckListService"];
checkListId: string;
constructor(
public $log: ng.ILogService,
public $loc: ng.ILocationService,
public checkListService: cs.ICheckListService) {
this.initialise();
}
/**
* initialise the controller component.
*/
async initialise() {
try {
var list = await this.checkListService.loadCheckLists();
this.checkListId = R.head(list).id.toString();
this.$log.info(`set check list id to ${this.checkListId}`);
} catch (error) {
// deal with problems here.
}
}
}
module("app").controller("checkListController", CheckListController)
回答by Jason Rice
Or you can just stick to the true ASYNC model and not overcomplicate the setup. 9 out of 10 times this comes down to asynchronous versus synchronous design. For example I have a React component that needed this very same thing were I was initializing the state variables in a promise callback in the constructor. Turns out that all I needed to do to get around the null data exception was just setup an empty state object then set it in the async callback. For example here's a Firebase read with a returned promise and callback:
或者,您可以坚持使用真正的 ASYNC 模型,而不必使设置过于复杂。10 次中有 9 次归结为异步与同步设计。例如,我有一个 React 组件需要同样的事情,我正在构造函数中的 promise 回调中初始化状态变量。事实证明,我需要做的只是设置一个空的状态对象来解决空数据异常,然后在异步回调中设置它。例如,这是一个带有返回的承诺和回调的 Firebase 读取:
this._firebaseService = new FirebaseService();
this.state = {data: [], latestAuthor: '', latestComment: ''};
this._firebaseService.read("/comments")
.then((data) => {
const dataObj = data.val();
const fetchedComments = dataObj.map((e: any) => {
return {author: e.author, text: e.text}
});
this.state = {data: fetchedComments, latestAuthor: '', latestComment: ''};
});
By taking this approach my code maintains it's AJAX behavior without compromising the component with a null exception because the state is setup with defaults (empty object and empty strings) prior to the callback. The user may see an empty list for a second but then it's quickly populated. Better yet would be to apply a spinner while the data loads up. Oftentimes I hear of individuals suggesting overly complicated work arounds as is the case in this post but the original flow should be re-examined.
通过采用这种方法,我的代码保持了它的 AJAX 行为,而不会因 null 异常而影响组件,因为在回调之前使用默认值(空对象和空字符串)设置状态。用户可能会看到一个空列表,但很快就会填充。更好的是在数据加载时应用微调器。我经常听到有人建议过于复杂的变通方法,就像这篇文章中的情况一样,但应该重新检查原始流程。