【问题标题】:How to return a Promise from async function?如何从异步函数返回 Promise?
【发布时间】:2023-03-08 04:13:01
【问题描述】:

当我尝试从异步函数返回 Promise 时,无法区分返回的 Promise 和函数的状态。

我认为,最简单的解决方案是将要返回的承诺放在一个数组中。以下是一个愚蠢的例子,但我希望它能说明问题:

function loadMetaData(id) {/*...*/} // Returns Promise<MetaData>
function loadSingleData(name) {/*...*/} // Returns Promise<SingleData>

async function startLoadingSingleData(id, object) {
    const metaData = object.metaData = await loadMetadata(id);
    const singleDataPromise = loadSingleData(metaData.dataToLoad);
    singleDataPromise.then(metaData => object.metaData = metaData);
    return [singleDataPromise];
}

async function logData(id) {
    const object = new SomeUsefulClassWhatTakesAdvantageOfMetadataProp();
    somePromise = (await startLoadingSingleData(id))[0];
    // Now metadata surely loaded, do something with it
    console.log(object.metaData);
    // But somedata will be loaded only in future
    somePromise.then(singleData => console.log(singleData));

    // And maybe: (depends of use-case)
    await somePromise;
}

在执行logData(/*...*/)时,首先是给定数据的给定ID的metaData,稍等片刻后,预计会出现完整的singleData

但这有点骇人听闻。

克服这种情况的预期方法是什么?

PS: 当我尝试返回一个通过 Promise 解决的 Promise 时,也会出现此问题。

【问题讨论】:

  • “无法区分返回的 Promise 和函数的状态”——我不明白你的意思。
  • “最简单的解决方案是将要返回的 Promise 放在一个数组中”——异步函数必须返回一个 Promise,而不是一个数组,即使该数组包含一个 Promise。
  • 异步函数自动返回一个 Promise。无论您从异步函数返回什么值,都将自动包装在 Promise 中。如果你真的做async function() { return new Promise(...) },你实际上是在返回一个Promise of a Promise。除非这是您需要的,否则您不需要自己创建承诺,只需返回您的值。
  • 谢谢大家,更新了。我希望现在这可以澄清返回承诺的必要性,以及我的意思。

标签: javascript es6-promise


【解决方案1】:

是的,不幸的是 JS 承诺不是代数的,you cannot fulfill a promise with another promise。没有办法解决这个问题(除了不使用原生承诺,也不使用async/await)。

最简单和最常见的解决方案确实是使用包装对象。你的问题很自然:

// takes an id, returns a Promise<{metaData: Data, singleDataPromise: Promise<Data>}>
async function startLoadingSingleData(id) {
    const object = new SomeUsefulClassWhatTakesAdvantageOfMetadataProp();
    object.metaData = await loadMetadata(id);
//                    ^^^^^
    object.singleDataPromise =   loadSingleData(object.metaData.dataToLoad);
//                             ^ no await here
    return object;
}
async function logData(id) {
    const object = await startLoadingSingleData(id));
    // Now metadata surely loaded, do something with it
    console.log(object.metaData);
    // But some singleData will be loaded only in future
    const singleData = await object.singleDataPromise;
    console.log(singleData);
}

请注意,如果您的代码中有异常并且您永远无法到达await singleDataPromise,这可能会导致未处理的拒绝问题。

(可能更好)的替代方法是重组你的函数,这样你就不会在使用(即等待)它们之前创建任何承诺,就像@Paulpro 也建议的那样。所以你只需要写一个严格的顺序函数

async function logData(id) {
    const object = new SomeUsefulClassWhatTakesAdvantageOfMetadataProp();
    object.metaData = await loadMetadata(id);
    // Now metadata surely loaded, do something with it
    console.log(object.metaData);
    // But some singleData will be loaded only in future
    object.singleData = await loadSingleData(object.metaData.dataToLoad);
    console.log(object.singleData);
}

【讨论】:

    【解决方案2】:

    你的问题是startLoadingSingleData的职责太多。它负责加载元数据和触发加载单数据​​。

    您的logData 函数使用await startLoadingSingleData(id) 作为确保元数据可用的一种方式,这似乎不太直观。 startLoadingSingleData(id) 返回一个在元数据加载后解析的 Promise 并不明显,并且对于第一次查看它的编码人员(或几个月后的您自己)来说会非常混乱。代码应该尽可能地自我记录,这样您就不需要用 cmets 解释每一行。

    我的建议是完全删除 startLoadingSingleData 函数并在 logData 内部执行此操作:

    async function logData(id) {
        const metaData = await loadMetadata(id);
        console.log(metaData);
    
        const singleData = await loadSingleData(metaData.name);
        console.log(singleData);
    }
    

    或者如果您不希望 logData 到 await SingleData Promise:

    async function logData(id) {
        const metaData = await loadMetadata(id);
        console.log(metaData);
    
        loadSingleData(metaData.name).then( singleData => {
            console.log(singleData);
        } );
    }
    

    如果你真的想继续使用函数startLoadingSingleData,那么我认为你应该让它返回一个数组或一个包含两个 Promise 的对象:

    function startLoadingSingleData(id) {
        const metaDataLoaded = loadMetadata(id);
        const singleDataLoaded = metaDataLoaded.then(
          metaData => loadSingleData(metaData.dataToLoad)
        );
    
        return { metaDataLoaded, singleDataLoaded };
    }
    

    那么你的用法会是这样的:

    async function logData(id) {
        const { metaDataLoaded, singleDataLoaded } = startLoadingSingleData(id);
    
        const metaData = await metaDataLoaded;
        console.log(metaData);
    
        const singleData = await singleDataLoaded;
        console.log(singleData);
    }
    

    【讨论】:

    • 好吧,我忘了:is a problem with the final snippeta, b = getPromises(); await a; await b;。当loadMetaData 拒绝时,您将收到来自singleDataLoaded 承诺的未处理承诺拒绝:-(
    猜你喜欢
    • 1970-01-01
    • 2020-07-09
    • 2017-10-16
    • 1970-01-01
    • 2018-04-13
    • 2019-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多