【问题标题】:Change type of promise returned by async functions to a different type将异步函数返回的承诺类型更改为不同的类型
【发布时间】:2019-01-10 18:16:59
【问题描述】:

编辑 - 虽然答案包含几个可行的解决方案,但我会指出 Parse JS SDK 2.0(以及因此 Parse-Server 3.0)已经发布,并且实际上删除了 Parse.Promise。因此,最好的解决方案是更换 Parse Promise 实现并改用本机 Promises。

我使用 Parse-Server(最新版本,2.8.2)作为后端。我非常习惯于 promise,它们在我的代码中根深蒂固。

不过,我已经开始改用 async / await 模式了。但它与我现有的所有实现都不能很好地配合。

这个例子会报错:

const promiseErrorTestHelper = async () => {
    return Parse.Promise.as();
}

Parse.Cloud.define('promiseErrorTest', async(req, res) => {
    promiseErrorTestHelper().always(
        res.success
    );
});

这个很好用:

const promiseErrorTestHelper = async () => {
    return Parse.Promise.as();
}

Parse.Cloud.define('promiseErrorTest', async(req, res) => {
    promiseErrorTestHelper().then(
        res.success
    );
});

在 Parse-Server 中,.always() 用于传递回调函数,无论 promise 被拒绝还是已解决。我在整个代码中都使用了它,并在重构某些函数以在添加新功能时使用 async / await 时发现了这个问题。

我理解问题在于异步函数将其结果包装在一个 Promise 中。所以,它把我的Parse.Promise 转换为没有.always() 方法的不同类型。

我是否有一种简单的方法可以覆盖异步的功能以返回 Parse.Promise 呢?或者,如果我想使用 async / await,我是否必须重新处理 always() 调用?

【问题讨论】:

  • 也许在async函数返回的内置promise中添加一个.always()方法。 Promise.prototype.always = function(fn) {...}.
  • 此外,无论承诺解决还是拒绝,您的许多处理都完全相同是不寻常的。所以,这让我觉得如果你一直使用.always(),你可能没有正确使用resolve和reject。
  • 看起来没有任何方法可以更改 async 函数返回的承诺类型。这是硬连线到 Javascript 中的。您可以使用包装器包装从您的async 函数返回的本机承诺,该包装器将其转换为另一种类型的承诺,但是这会将代码添加到您想要使用的每个await
  • 仅供参考,这里有一个类似的讨论,关于人们试图让 async 函数从 Bluebird 承诺库返回承诺:github.com/petkaantonov/bluebird/issues/1434。结论 - 如果不将返回的承诺包装在另一个承诺中,就无法做到这一点。
  • @jfriend00 非常感谢您做一些研究!您首先提到的是我希望我能够做到的事情,遗憾的是我无法掌握异步返回的内容。这不是我们使用always 的核心功能。例如,向我们发送有关活动的警报消息或 ping 我们的事件服务器以跟踪事件。在完成所有数据库更新后,我不想停止函数,因为我的事件 ping 不知何故失败了。我知道这不是理想的解决方案,但对我们来说很快。它只是与 async/await 不兼容。

标签: node.js promise async-await parse-server


【解决方案1】:

您似乎无法更改 async 函数返回的承诺类型。它是内置的以返回本机​​承诺并且不可配置。关于如何使 async 函数返回 Bluebird 承诺 here,这里有类似的讨论。结论是,如果你想要一个 Bluebird 承诺,你不能这样做,你必须包装 async 函数。


要在错误后继续,通常的方法是使用 .catch() 记录错误并且不重新抛出,从而“处理”错误:

someFunc().catch(err => {
    // handle the error, continue processing, changes promise chain to resolved
    console.log(err);
    return null;
}).then(() => {
    // will always get called when original promise resolves or rejects
    // somewhat like your .always
});

或者,您可以包装您的异步函数以返回您想要的承诺类型:

function wrapFunc(arg) {
    return Parse.Promise.resolve(someAsyncFunc(arg))
}

然后,改为调用包装函数。


我不确定我是否建议这样做,因为您正在修改全局可访问的 Promise 原型,这可能会影响其他代码,但您也可以将 .always() 添加到内置的 Promise 中。

Promise.prototype.always = function(fn) {
    return this.then(fn, fn);
}

这实际上是完全相同的实现that Parse uses。然后,您可以对从 async 函数返回的承诺使用 .always()

【讨论】:

  • 我在答案中添加了几个新选项。一个用于将异步函数包装在返回 Parse 承诺的新函数中,另一个用于将 .always() 添加到 async 函数返回的本机承诺中。
  • 哦,我喜欢这个新的例子!这看起来像我正在寻找的快速解决方案。我想知道,这会有多“危险”?当前是否有任何尝试访问.always() 的东西现在会崩溃?我想不出如果以前不存在它会被调用的情况,即使是这样,也无法真正想到为什么它会做更多的事情而不是这里发生的事情。
  • @JakeT。 - 这只是修改全局对象的常见问题。如果多个库尝试修改它并且他们尝试以不兼容的方式进行修改,那么您会在同一项目中使用的不同库之间发生冲突。如果你不知道你正在使用的其他东西也在尝试这样做,你可以安全地做到这一点。这对于一个较小的项目来说是可以管理的,但对于一个有很多人参与的大型项目来说,这要困难得多。
  • 这个问题没过多久就变得没有实际意义,Parse JS SDK 2.0 即将推出,并删除了 Parse.Promise 以支持原生承诺。所以我想我真正的答案是删除任何对always....的调用。
【解决方案2】:

jfriend00 的回答是一个可以接受的解决方案。 .catch(value => {return value}).then(...) 将基本上起到.always() 的作用。

就我而言,我不想这样做,因为这意味着我要在许多地方修补代码以解决源自某个地方的问题。我正在修改一个现有的函数,该函数使用Parse.Promises 来使用 async / await,并且我也向它添加了一些功能。在我完成之前我没有发现.always() 问题。我不想简单地扔掉返工并用承诺再次重做更新。

虽然草率,但我最终将我的辅助函数包装在它自己的辅助函数中。原来的辅助函数是 not 异步的,它返回一个基于异步辅助函数的结果解析的 Promise。

const promiseErrorTestHelper = async (<same params>) => {
    let returnPromise = new Parse.Promise();

    promiseErrorTestHelperHelper(<same params>).then(
        success => { returnPromise.resolve(success); },
        error => { returnPromise.reject(error); }
    );

    return returnPromise;
}

const promiseErrorTestHelperHelper = async () => {
    // This can return whatever, body and parameters
    // copied in entirety from parent function
}

Parse.Cloud.define('promiseErrorTest', async(req, res) => {
    promiseErrorTestHelper().then(
        res.success
    );
});

这使我能够以与以前相同的方式调用原始辅助函数,但实现已更新为更简洁且更具可读性。

不过,有一件事是我无法使用函数式编程,我最近一直在学习并试图将其付诸实践。这不起作用:

const promiseErrorTestHelper = async (<same params>) => {
    let returnPromise = new Parse.Promise();

    promiseErrorTestHelperHelper(<same params>).then(
        returnPromise.resolve,
        returnPromise.reject
    );

    return returnPromise;
}

我的理解是,这应该与上面的几乎相同,只是没有被包装在匿名函数中。当然,变量没有命名,但无论哪种方式都应该传递给这些方法,对吧?我在其他地方使用这种模式,但不是为了 promise.resolve / reject,因为我只是直接返回。但是我有很多.then( response.success, response.error ) 调用我的云功能,而不是像上面的工作示例中那样,将returnPromise.resolveresponse.success 交换,将returnPromise.rejectresponse.error 交换。这些工作正常。

【讨论】:

  • 您看到我添加到答案中的选项了吗?仅供参考,您的包装可以做得更简洁(请参阅我的回答中的包装解决方案)。
  • 我做了,就在我发布我的之后。我确实想过这样包装我的承诺,但是当我开始使用这种方法时,我故意让它仍然 return 错误,并在我不希望它失败时使用.always()是一个失败的案例。我认为该函数不需要知道我打算在大多数情况下忽略该错误,它应该只是“完成它的工作”(尽管我可能应该使用更简洁的工作)。有几个地方我确实调用了这个函数并且失败了。 IE。我的一些云功能,其中事件日志是事件发生的唯一指示。
  • 我并不是说我正在做最好的方式。我只是想尽可能地匹配以前的功能。你知道为什么我不得不创建匿名函数而不是能够将returnPromise.resolve/reject 传递给.then() 吗?当我刚刚尝试return promiseErrorTestHelperHelper.then(...); 时它也失败了,尽管现在我想起来了,我试图传递函数而不是做回调,所以也许就是这样,我不必显式地返回一个承诺。
  • @jfriend00 嘿,我真的很感谢你的时间和多个答案。超出我的预期。
猜你喜欢
  • 2021-06-17
  • 1970-01-01
  • 2017-09-16
  • 1970-01-01
  • 2020-01-01
  • 1970-01-01
  • 2019-05-07
  • 2021-07-16
相关资源
最近更新 更多