【问题标题】:Cloud HTTPS Functions: returning a Promise which is inside a PromiseeCloud HTTPS 函数:返回一个 Promise 中的 Promise
【发布时间】:2020-06-20 14:16:15
【问题描述】:

我目前正在使用 Firebase 开发 HTTPS 云功能,包括删除我的 Android 用户请求的帖子。

总体思路

工作流程是(整个代码在这个 SO 问题的末尾可用):1)Firebase 检查用户身份(admin.auth().verifyIdToken); 2) Firestore 从帖子中获取必须删除的数据 (deleteDbEntry.get().then()) ; 3)云存储准备删除获取到的数据中的文件(.file(filePath).delete()); 4) Firestore 准备一批删除帖子 (batch.delete(deleteDbEntry);) 并使用获取的数据 (batch.update(updateUserLikes,) 更新喜欢/不喜欢的人; 5)执行删除文件和批处理的承诺(return Promise.all([deleteFile, batch_commit]))。

预期行为

我想检查用户身份。如果成功,则使用 Firebase 获取请求的帖子以删除数据。如果成功,我想在同一个承诺中执行 Firestore 批处理和 Cloud Storage 文件删除(这就是我使用 Promise.all([deleteFile, batch_commit]).then() 的原因)。如果身份检查失败,或者数据获取失败,或者批处理失败,我想告诉 Android 应用程序。如果一切顺利,同上。

由于所有这些操作都在 Cloud HTTPS 函数中,我必须返回一个承诺。我认为,如果这些操作成功,则该承诺将对应于所有操作,如果至少有一个操作不成功,则对应于错误(?)。

实际行为

目前,我只是返回 Firebase 用户身份检查的承诺。

我的问题和我的问题

我无法从实际行为转变为预期行为,因为:

  1. 我觉得在这个 Cloud HTTPS Function 中是否应该返回与“所有这些操作都成功,或者至少有一个不成功”对应的 Promise,我觉得不是很清楚

  2. 由于这些操作是嵌套的(Firestorage 文件删除 + Firestore 后删除批量存在),我无法返回类似Promise.all() 的内容。

我的问题

您能否告诉我我是否正确(第 1 点),如果不是:我该怎么办?如果是:由于第 2 点,我该怎么做?

整个 Firebase Cloud HTTPS 功能代码

注意:我删除了输入数据控件以使我的代码更清晰。

exports.deletePost = functions.https.onCall((data, context) => {

    return admin.auth().verifyIdToken(idToken)
          .then(function(decodedToken) {
            const uid = decodedToken.uid;

            const type_of_post = data.type_of_post;
            const the_post = data.the_post;
            const deleteDbEntry = admin_firestore.collection('list_of_' + type_of_post).doc(the_post);
            const promise = deleteDbEntry.get().then(function(doc) {

                const filePath = type_of_post + '/' + uid + '/' + data.stored_image_name;
                const deleteFile = storage.bucket('android-f.appspot.com').file(filePath).delete();

                const batch = admin.firestore().batch();
                batch.delete(deleteDbEntry);
                if(doc.data().number_of_likes > 0) {
                    const updateUserLikes = admin_firestore.collection("users").doc(uid);
                    batch.update(updateUserLikes, "likes", FieldValue.increment(-doc.data().number_of_likes));
                }
                const batch_commit = batch.commit();

                return Promise.all([deleteFile, batch_commit]).then(function() {
                    return 1;
                }).catch(function(error) {
                    console.log(error);
                    throw new functions.https.HttpsError('unknown', 'Unable to delete the post. (2)');
                });

            }).catch(function(error) { 
                console.log(error);
                throw new functions.https.HttpsError('unknown', 'Unable to delete the post. (1)');
            });

            return promise;


          }).catch(function(error) {
                console.log(error);
            throw new functions.https.HttpsError('unknown', 'An error occurred while verifying the token.');
          });
});

【问题讨论】:

    标签: javascript android firebase google-cloud-firestore google-cloud-functions


    【解决方案1】:

    您应该注意,您实际上是在定义 Callable Cloud Function 而不是 HTTPS one,因为您这样做了:

    exports.deletePost = functions.https.onCall((data, context) => {..});
    

    Callable Cloud Function 优于 HTTPS 的优点之一是它“自动反序列化请求正文并验证身份验证令牌”。

    因此您可以简单地使用context.auth.uid; 获取用户uid


    现在,关于“协调”不同调用的方式,恕我直言,您应该将异步 Firebase 方法(Firestore 的方法和 Cloud Storage 的方法)返回的不同 Promise 链接起来,如下所示:

    exports.deletePost = functions.https.onCall((data, context) => {
    
        //....
        const uid = context.auth.uid;
    
        let number_of_likes;
    
        const type_of_post = data.type_of_post;
        const the_post = data.the_post;
        const deleteDbEntry = admin_firestore.collection('list_of_' + type_of_post).doc(the_post);
    
        return deleteDbEntry.get()
            .then(doc => {
                number_of_likes = doc.data().number_of_likes;
                const filePath = type_of_post + '/' + uid + '/' + data.stored_image_name;
                return storage.bucket('android-f.appspot.com').file(filePath).delete();
    
            })
            .then(() => {
    
                const batch = admin.firestore().batch();
                batch.delete(deleteDbEntry);
    
                if (number_of_likes > 0) {
                    const updateUserLikes = admin_firestore.collection("users").doc(uid);
                    batch.update(updateUserLikes, "likes", FieldValue.increment(-doc.data().number_of_likes));
                }
    
                return batch.commit();
    
            }).catch(function (error) {
                console.log(error);
                throw new functions.https.HttpsError('....', '.....');
            });
    
    });
    

    我不认为使用Promise.all() 会带来任何兴趣,在你的情况下,因为,正如here 所解释的,“如果任何传入的承诺拒绝,Promise.all 异步拒绝与被拒绝的承诺,其他承诺是否已解决”。

    在撰写本文时,无法将所有这些对不同 Firebase 服务的异步调用组合成一个原子操作。

    即使最后的批量写入是原子的,也可能会发生 Cloud Storage 中的文件被正确删除但未执行对 Firestore 的批量写入,例如因为 Firestore 服务出现问题。


    另外,请注意在 Promise 链的末尾只需要一个异常处理程序。如果您想区分异常的原因,可以向前端发送不同的错误消息,您可以使用article 中介绍的方法。

    本文展示了如何定义不同的自定义错误类(派生自标准内置错误对象),用于检查异常处理程序中的错误类型。

    【讨论】:

    • @JarsOfJam-Scheduler 看看更新。我刚刚意识到您需要将来自deleteDbEntry 文档的值传递给第二个then()。您还可以在删除 Cloud Storage 文件之前执行批量写入:像这样不需要“全局”number_of_likesvariable。
    猜你喜欢
    • 2021-10-18
    • 1970-01-01
    • 1970-01-01
    • 2017-09-13
    • 1970-01-01
    • 2021-08-04
    • 2020-10-04
    • 2018-05-20
    • 2023-03-21
    相关资源
    最近更新 更多