英文:
How to await a value being set asynchronously?
问题
预期常见问题:
- 是的,我知道 Promise 是什么。
- 不,我不能简单地将初始化逻辑移到构造函数中。它需要在
initMethod
中调用,因为initMethod
是需要在特定时间调用的钩子。
抱歉,只是因为我看到一些类似的问题被标记为“重复”,所以我想把这些常见问题放在最前面。
问题
我的问题是以下的竞态条件:
class Service {
private x: string | null = null;
initMethod() {
this.x = 'hello';
}
async methodA() {
return this.x.length;
}
}
const service = new Service();
setTimeout(() => service.initMethod(), 1000);
service.methodA().catch(console.log);
TypeError: Cannot read properties of null (reading 'length')
at Service.methodA (<anonymous>:15:19)
at <anonymous>:20:9
at dn (<anonymous>:16:5449)
我需要类似 Promise 的东西,其已解决的值可以从代码的另一部分设置。类似于:
class Service {
private x: SettablePromise<string> = new SettablePromise();
initMethod() {
this.x.set('hello');
}
async methodA() {
return (await this.x).length;
}
}
const service = new Service();
setTimeout(() => service.initMethod(), 1000);
service.methodA().catch(console.log);
我能想到的最好的方法是创建一个类,轮询一个值,直到它变为非空。我希望有更智能的方法。我不想调整轮询间隔。
编辑
对于最初的混乱示例表示抱歉。竞态条件是 methodA
可能在 initMethod
之前被调用。
initMethod
中也有一个不必要的 async
。我只是把它设为 async
,因为它基于的真实方法是 async
的。
英文:
Anticipated FAQ:
- Yes, I know what a Promise is.
- No, I can't simply move the init logic to the constructor. It needs to be called in the
initMethod
because theinitMethod
is a hook that needs to be called at a certain time.
Sorry, it's just that I saw some similar questions marked as "duplicate", so I wanted to put these FAQ at the top.
Question
My issue is the following race condition:
class Service {
private x: string | null = null;
initMethod() {
this.x = 'hello';
}
async methodA() {
return this.x.length;
}
}
const service = new Service();
setTimeout(() => service.initMethod(), 1000);
service.methodA().catch(console.log);
TypeError: Cannot read properties of null (reading 'length')
at Service.methodA (<anonymous>:15:19)
at <anonymous>:20:9
at dn (<anonymous>:16:5449)
I need something like a Promise whose settled value can be set from another part of the code. Something like:
class Service {
private x: SettablePromise<string> = new SettablePromise();
initMethod() {
this.x.set('hello');
}
async methodA() {
return (await this.x).length;
}
}
const service = new Service();
setTimeout(() => service.initMethod(), 1000);
service.methodA().catch(console.log);
The best I can come up with is to make a class that polls a value until it turns non-null. I'm hoping there's something smarter. I don't want to have to fine-tune a poll interval.
Edits
Sorry for the initial confusing example. The race condition is that methodA
can be called before initMethod
.
There was also an unnecessary async
in the initMethod
. I just made it async
because the real method it was based on is async
.
答案1
得分: 2
以下是翻译好的内容:
在以下示例中,您可以在异步方法调用之前或之后运行init
。两种方式都可以工作 -
const s = new Service()
// 初始化
s.init()
// 然后查询
s.query("SELECT * FROM evil").then(console.log)
const s = new Service()
// 首先查询
s.query("SELECT * FROM evil").then(console.log)
// 然后初始化
s.init()
deferred
解决方案始于通用的deferred
值,允许我们在外部解析或拒绝一个Promise -
function deferred() {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
service
现在我们将编写一个Service
类,它有一个resource
延迟值。init
方法将在某个时间点解析资源。异步方法query
将在继续之前等待资源 -
class Service {
resource = deferred() // 延迟资源
async init() {
this.resource.resolve(await connect()) // 解析资源
}
async query(input) {
const db = await this.resource.promise // 等待资源
return db.query(input)
}
}
connect
这只是我们在init
方法中运行的一些示例操作。它返回一个具有模拟数据库调用的query
方法的对象 -
async function connect() {
await sleep(2000) // 模拟延迟
return {
query: (input) => {
console.log("运行查询:", input)
return ["hello", "world"] // 模拟数据结果
}
}
}
function sleep(ms) {
return newPromise(r => setTimeout(r, ms))
}
demo
function deferred() {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
class Service {
resource = deferred()
async init() {
this.resource.resolve(await connect())
}
async query(input) {
const db = await this.resource.promise
return db.query(input)
}
}
async function connect() {
await sleep(2000)
return {
query: (input) => {
console.log("运行查询:", input)
return ["hello", "world"]
}
}
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
const s = new Service()
s.query("SELECT * FROM evil").then(console.log)
s.init()
始终处理拒绝
如果init
无法连接到数据库,我们需要通过资源来反映这一点,否则我们的程序将无限期挂起 -
class Service {
resource = deferred()
async init() {
try {
const db = await timeout(connect(), 5000) // 超时
this.resource.resolve(db)
}
catch (err) {
this.resource.reject(err) // 拒绝错误
}
}
async query(input) {
const db = await timeout(this.resource.promise, 5000) // 超时
return db.query(input)
}
}
function timeout(p, ms) {
return Promise.race([
p,
sleep(ms).then(() => { throw Error("超时") }),
])
}
英文:
In the following example, you can run the init
before or after the async method call. Either will work -
const s = new Service()
// init
s.init()
// then query
s.query("SELECT * FROM evil").then(console.log)
const s = new Service()
// query first
s.query("SELECT * FROM evil").then(console.log)
// then init
s.init()
deferred
The solution begins with a generic deferred
value that allows us to externally resolve or reject a promise -
function deferred() {
let resolve, reject
const promise = new Promise((res,rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
service
Now we will write Service
which has a resource
deferred value. The init
method will resolve the resource at some point in time. The asynchronous method query
will await the resource before it proceeds -
class Service {
resource = deferred() // deferred resource
async init() {
this.resource.resolve(await connect()) // resolve resource
}
async query(input) {
const db = await this.resource.promise // await resource
return db.query(input)
}
}
connect
This is just some example operation that we run in the init
method. It returns an object with a query
method that mocks a database call -
async function connect() {
await sleep(2000) // fake delay
return {
query: (input) => {
console.log("running query:", input)
return ["hello", "world"] // mock data result
}
}
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
demo
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function deferred() {
let resolve, reject
const promise = new Promise((res,rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
class Service {
resource = deferred()
async init() {
this.resource.resolve(await connect())
}
async query(input) {
const db = await this.resource.promise
return db.query(input)
}
}
async function connect() {
await sleep(2000)
return {
query: (input) => {
console.log("running query:", input)
return ["hello", "world"]
}
}
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
const s = new Service()
s.query("SELECT * FROM evil").then(console.log)
s.init()
<!-- end snippet -->
always handle rejections
If init
fails to connect to the database, we need to reflect that with the resource, otherwise our program will hang indefinitely -
class Service {
resource = deferred()
async init() {
try {
const db = await timeout(connect(), 5000) // timeout
this.resource.resolve(db)
}
catch (err) {
this.resource.reject(err) // reject error
}
}
async query(input) {
const db = await timeout(this.resource.promise, 5000) // timeout
return db.query(input)
}
}
function timeout(p, ms) {
return Promise.race([
p,
sleep(ms).then(() => { throw Error("timeout") }),
])
}
答案2
得分: 1
根据我理解,您需要在构造函数中解决一个Promise,但只有在调用initMethod
时才解决它。
您可以在Promise旁边公开一个Promise解决器:
class Service {
private x: string | null = null;
private promise;
private resolve;
constructor() {
this.promise = new Promise(resolve => this.resolve = resolve);
}
async initMethod() {
// 进行异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
this.x = 'hello';
// 然后解决Service的Promise
this.resolve();
}
async methodA() {
await this.promise;
return this.x.length;
}
}
英文:
As I understand it, you need to have a promise settled within constructor but it have to be resolved only when initMethod
is called.
You can expose a promise resolver alongside the promise :
class Service {
private x: string | null = null;
private promise;
private resolve;
constructor() {
this.promise = new Promise(resolve => this.resolve = resolve);
}
async initMethod() {
// Do your async stuff
await new Promise(resolve => setTimeout(resolve, 1000));
this.x = 'hello';
// And resolve Service promise
this.resolve();
}
async methodA() {
await this.promise;
return this.x.length;
}
}
See https://stackoverflow.com/questions/26150232/resolve-javascript-promise-outside-the-promise-constructor-scope for more examples.
答案3
得分: 1
你需要使用一个 Promise
值来初始化一个属性,该属性只有在 initMethod()
完成后才会解析。
这还涉及维护 promise 的 resolve
回调作为类属性。
class Service {
#initResolve;
#initPromise = new Promise((resolve) => {
this.#initResolve = resolve;
});
#x = null;
async initMethod() {
await new Promise(resolve => setTimeout(resolve, 1000));
this.#x = 'hello';
this.#initResolve(); // 解析 #initPromise
}
async methodA() {
await this.#initPromise;
return this.#x.length;
}
}
const service = new Service();
console.log("methodA: start");
service.methodA().then(console.log.bind(console, "methodA: end:"));
setTimeout(async () => {
console.log("initMethod: start");
await service.initMethod()
console.log("initMethod: end");
}, 1000);
备注:代码中使用 JavaScript 私有类特性,但我相信你可以将其翻译成 TypeScript。
英文:
You need to initialise a property with a Promise
value that only resolves after initMethod()
has completed.
This involves also maintaining the promise's resolve
callback as a class property.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
class Service {
#initResolve;
#initPromise = new Promise((resolve) => {
this.#initResolve = resolve;
});
#x = null;
async initMethod() {
await new Promise(resolve => setTimeout(resolve, 1000));
this.#x = 'hello';
this.#initResolve(); // resolve #initPromise
}
async methodA() {
await this.#initPromise;
return this.#x.length;
}
}
const service = new Service();
console.log("methodA: start");
service.methodA().then(console.log.bind(console, "methodA: end:"));
setTimeout(async () => {
console.log("initMethod: start");
await service.initMethod()
console.log("initMethod: end");
}, 1000);
<!-- end snippet -->
Note: Using JavaScript private class features for the Snippet but I'm sure you can translate it to Typescript.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论