如何从异步函数返回Promise?

时间:2018-03-28 18:22:36

标签: javascript es6-promise

当我尝试从异步函数返回一个promise时,无法区分返回的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

但这有点像个hackish。

克服这种情况的目的是什么?

PS .: 当我尝试返回一个以promise为结果的Promise时,也会出现此问题。

2 个答案:

答案 0 :(得分:3)

是的,遗憾的是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);
}

答案 1 :(得分:1)

你的问题是startLoadingSingleData责任太多了。它负责加载元数据和触发singledata的加载。

您的logData函数使用await startLoadingSingleData(id)作为确保元数据可用的方法,这似乎不太直观。 startLoadingSingleData(id)返回一个在元数据加载时解析的Promise并不明显,对于第一次看到它(或者几个月后你自己)的编码器来说会很困惑。代码应该尽可能自我记录,这样您就不需要用注释解释每一行。

我的建议是完全删除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,那么我认为你应该让它返回一个数组或一个包含两个Promises的对象:

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);
}