首页 > 解决方案 > 在外部解决承诺的 JavaScript“惯用”方式是什么?

问题描述

在一个程序中,我发现外部可解决承诺的概念(因为没有更好的词)很有用。用例是:程序中的多个位置需要某个值,但在一个特定位置(作为其他工作的一部分)异步计算。所以这个想法是预先定义一个全局变量来保存值的承诺:

让变量 = settlablePromise();

(其中 settlablePromise 是一个实用函数 - 见下文)。

无论在哪里需要值,我们都可以简单地 .then 或 await 变量(因为它已被分配了一个承诺)。

程序中计算值的地方将调用 variable.settle(x) 其中 x 是计算值。

这个方案有效,但我想知道是否有更标准的方法,而不是这个“settlablePromise”函数,如下所示。它的功能是产生一个可以在外部解决的承诺:

function settlablePromise()
{
    let resolver = null;
    let p = new Promise( (resolv,err) => {
        resolver  = resolv;
    });
    p.settle = function(v)
    {
        resolver(v);
    };
    return p;
}

标签: javascriptasynchronousasync-await

解决方案


该方案有效,但我想知道是否有更标准的方法

很大程度上,答案是:不要。:-) 相反,这样做:

const variable = xyz();

...并制作xyz可以完成获取值工作的代码部分,而不是variable.settle(x)稍后调用该代码。暴露结算方法的问题是它们可以被任何代码调用,可能不止一次,但只有一个调用会对承诺产生影响。因此,设计有意使那些对创建 Promise 的代码私有。如果没有启动一些可以报告完成的异步操作,则该承诺是没有意义的。

如果将执行异步工作的代码尚未准备好立即开始工作,您仍然可以xyz返回一个承诺并让该代码稍后启动该过程。结算方法将对该代码保持私有,而不是暴露给所有代码,保持封装。

这是一个罕见的用例,通常您希望在创建 Promise 时开始工作。但在这种罕见的情况下,您可以在不暴露结算功能的情况下做到这一点。

例如:

// In a module specific to the asynchronous work
let [promise, start] = (() => {
    let start;
    const promise = new Promise((resolve, reject) => {
        start = () => {
            // ...Actually start the work, handle completion by calling
            // `resolve` or `reject...
        };
    });
    return [promise, start];
})();
export function xyz() { // The public face of the work
    return promise;
}

// Code elsewhere in the module starts the process via `start`

你甚至可以直接暴露 Promise,而不是让它成为函数调用的结果:

// In a module specific to the asynchronous work
let [promiseOfNiftyStuff, start] = (() => {
    let start;
    const promise = new Promise((resolve, reject) => {
        start = () => {
            // ...Actually start the work, handle completion by calling
            // `resolve` or `reject...
        };
    });
    return [promise, start];
})();
export { promiseOfNiftyStuff };

// Code elsewhere in the module starts the process via `start`

同样,这是一个罕见的用例。

Yury Tarabanko提供了一个很好的例子来说明“罕见”用例,您需要将结算处理程序泄漏到包含的上下文中,并且仍然保持良好的封装(在本例中为fromEvent):

async function* fromEvent(element, event) {
    let resolve = null;

    element.addEventListener(event, (event) => {
        resolve(event);
    });

    while (true) {
        yield new Promise(r => {
            resolve = r;
        })
    }
}

async function run() {
    const button = document.getElementById('test');

    for await (const event of fromEvent(button, 'click')) {
        console.log('clicked');
    }
}

run();
<button id="test">Click</button>


推荐阅读