在JavaScript中,有没有办法创建一个自定义的thenable,在等待后自动触发一些代码?

问题描述 投票:0回答:1

考虑以下代码:

async function test(label) {
    console.log(`BEFORE: ${label}`);
    await waitForSomething(label);
    console.log(`AFTER: ${label}`);
}

function waitForSomething(label) {
    return {
        then(onFulfilled) {
            console.log(`FULFILL: ${label}`);
            onFulfilled();
        }
    }
}

console.log('START');
test('A');
test('B');
console.log('END');

它给出以下输出:

开始
之前:A
之前:B
结束
满足:A
满足:B
之后:A
之后:B

我希望

AFTER A
FULFILL A
之后立即触发,以同步方式,这将给出:

开始
之前:A
之前:B
结束
满足:A
之后:A
满足:B
之后:B

我的想法是,我正在编写一个具有全局状态的库,我希望在用户编写的异步调用链中保持一致。由于多个链可能同时运行,因此我需要在调用链“恢复”之前运行一些代码。我知道乍一看这似乎是一个奇怪或坏主意,但这是一个单独的问题。有办法做到吗?

我想过返回一个带有类似

unwrap
方法的对象,该方法会触发代码,例如:

(await waitForSomething(label)).unwrap();

但这迫使用户在所有相关调用上使用

unwrap()
,这很乏味。此外,他们可能会忘记这样做,并在不明白原因的情况下遇到意想不到的问题。

编辑:我将进一步阐述这背后的动机。基本上,这个想法是避免必须将状态参数一直传递到链并使其作为全局变量进行访问。

而不是这样做:

async function test(state) {
    let input = await waitForUserInput();
    doSomethingWith(state, input);
}

function doSomethingWith(state, input) {
    // ...
}

我可以做:

async function test() {
    let input = await waitForUserInput();
    doSomethingWith(input);
}

function doSomethingWith(input) {
    let state = MyLib.state;
    // ...
}

这可以避免在

state
触发的每个函数调用中携带
test
。然而,由于其中一些函数是异步的,并且由于其中多个函数可以同时运行,并且由于每个根函数都有不同的状态(例如
test
),因此我需要将
MyLib.state
设置为右侧异步函数返回时的值。同样,这是在库的上下文中:库向
test
函数提供状态并运行它,但
test
本身是由用户编写的。

编辑2:正如@user3840170所指出的,事实证明我需要的是https://github.com/tc39/proposal-async-context/,这仅处于提案的第二阶段。所以我想现在这是不可能的。

javascript promise thenable
1个回答
0
投票

我想目标不是按照您想要的顺序制作日志,而是在异步调用堆栈中保留状态。您可以在

await
之后立即将恢复状态的微任务排队。我想需要一个更详细的解决方案来满足您的所有需求:

async function test(label) {
    console.log(`BEFORE: ${state}`);
    await waitForSomething();
    console.log(`AFTER: ${label} => ${state}`);
}

function waitForSomething() {
    let cached = state;
    return {
        then(onFulfilled) {
            console.log(`FULFILL: ${cached}`);
            // call this before the callback, so the code after `await` would receive the proper state
            queueMicrotask(() => state = cached);
            onFulfilled();
        }
    }
}

console.log('START');
let state = 'A'; // use myLib.state instead in the real code
test('A');
state = 'B';
test('B');
console.log('END');
.as-console-wrapper{max-height:100% !important}

© www.soinside.com 2019 - 2024. All rights reserved.