Возврат обещания при использовании async / await в Firebase Cloud Functions

Итак, я с радостью использую async / await, поскольку узел 8 поддерживается в Firebase Cloud Functions. Однако я борюсь с одной вещью. При использовании вызываемых функций сообщается, что вы должны вернуть обещание в функции, иначе оно не будет работать правильно. При использовании необработанных обещаний мне ясно, как их использовать:

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return promiseMethod().then((result) => {
        return nextPromise(result);
    }).then((result) => {
        return result;
    }).catch((err) => {
        // handle err
    })
});

Но теперь, с async await, я не уверен, как вернуть эту «цепочку обещаний»:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);
    return res2;
    // ??? Where to return the promise?
});

Кто-нибудь знает?

«Я не уверен, как вернуть эту« цепочку обещаний »:» - Точно так же, как вы сделали в написанном вами коде.

Quentin 21.08.2018 15:18

Хорошо, но журналы на консоли firebase говорят мне, что я вызываю функцию 2 раза, хотя на самом деле я вызываю ее только 1 раз. Я думал, что это как-то связано с неправильным возвратом этих обещаний.

Jaap Weijland 21.08.2018 15:21
Бросьте бессмысленный .then((result) => { return result; })
Bergi 21.08.2018 16:10

Вы можете легко создать пример проекта и проверить утверждения ответов. Просто создайте функцию, которая будет ждать 10 секунд, а затем вернет результат. Если вы вызовете эту функцию с помощью await, функция firebase правильно подождет 10 секунд, а затем вернет результат.

Falco 08.01.2021 10:53

Я добавил к своему ответу авторитетный образец кода от команды google-firebase. Если этого недостаточно, я не знаю.

Falco 08.01.2021 10:59
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
13
5
7 014
10

Ответы 10

Функции HTTP не возвращают обещание. Они просто отправляют результат. Вам все равно нужно правильно использовать обещания, чтобы отправить результат, но возвращаемое значение не требуется. Функции HTTP завершаются при отправке ответа. См. документация для более подробной информации:

Terminate HTTP functions with res.redirect(), res.send(), or res.end().

То же самое и с .onCall-функциями? Когда я вызываю свою функцию один раз, регистратор на панели управления firebase сообщает, что моя функция вызывается дважды. Я считал, что это как-то связано с возвратом этих обещаний.

Jaap Weijland 21.08.2018 10:11

Нет, вызываемые функции требуют, чтобы вы возвращали обещание, которое разрешается с данными, которые вы хотите отправить клиенту. Это должно быть ясно из документации.

Doug Stevenson 21.08.2018 15:05

Именно поэтому мой первоначальный вопрос относится к вызываемым функциям: как вернуть это обещание при использовании async / await

Jaap Weijland 21.08.2018 15:14

Итак, вы отредактировали вопрос, чтобы показать вызываемую функцию вместо функции HTTP, которая у вас была изначально. Фактически вам задают другой вопрос вместо первого, что на самом деле не так, как работает Stack Overflow. Если вы полностью измените условия вопроса, вы запутаете всех.

Doug Stevenson 21.08.2018 15:23

Я думал, что HTTPS также должен возвращать обещание; для меня это была мелочь, но факт отмечен.

Jaap Weijland 21.08.2018 15:56

HTTPS и вызываемый очень разные способы отправки данных клиенту. Найдите время, чтобы изучить документацию и образцы, чтобы прояснить это.

Doug Stevenson 21.08.2018 16:02

@DougStevenson В документе firebase указано, что вы можете вернуть обещание для HTTP-функций и просто убедитесь, что это обещание в конечном итоге вызовет response.end() или что-то подобное.

Falco 15.04.2019 10:11

При необходимости просто конвертируйте в Promise.

Т.е. Если nextPromise возвращает обещание:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    return nextPromise(res1);
});

С другой стороны, если nextPromise является асинхронной функцией, просто преобразуйте ее в Promise:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    return Promise.resolve(nextPromise(res1));
});

вы также можете преобразовать результат:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);
    return Promise.resolve(res2);
});

Это совершенно неуместно: асинхронная функция вернет Promise вызывающей стороне при первом «ожидании», которое в конечном итоге разрешит все, что вы вернете в операторе return. Заключение этого обещания в другое обещание ничего не дает.

Falco 15.04.2019 10:08

Я привел 3 примера. первый оптимален, два других - нет, и здесь просто для пояснения, что есть несколько способов сделать то же самое.

Erez 16.04.2019 14:02

Promise.resolve совершенно не нужен и вводит в заблуждение. Вы можете просто удалить его в обоих случаях, без каких-либо минусов. Это просто ненужный код, отвлекающий от работы асинхронных функций.

Falco 16.04.2019 18:03

"await" - это просто синтаксический сахар для возврата обещания

Когда вы пишете асинхронную функцию, код фактически выйдет из функции и вернет обещание при первом встретившемся ожидании. Весь код после ожидания будет преобразован в then ().

Таким образом, для firebase написание кода с помощью async / await отлично сохраняется и, по моему опыту, даже менее подвержено ошибкам, так как я могу легче структурировать try & catch в своем коде!

Доказательство:

Просто запустите это в своей консоли:

async function iAmAsync() {
  await new Promise(r => window.setTimeout(r, 1000))
  return 'result'
}

let x = iAmAsync()
console.info(x)

Напечатает: Promise{<resolved>: "result"}

TL; DR: вам не нужно ничего менять - если вы напишете код с несколькими ожиданиями, это будет обрабатываться firebase как цепочка обещаний, и все будет просто работать.

И поскольку мой ответ был отклонен, вот авторский образец кода самой команды Google Firebase:

https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase/functions/index.js

exports.addMessage = functions.https.onRequest(async (req, res) => {
// [END addMessageTrigger]
  // Grab the text parameter.
  const original = req.query.text;
  // [START adminSdkPush]
  // Push the new message into the Realtime Database using the Firebase Admin SDK.
  const snapshot = await admin.database().ref('/messages').push({original: original});
  // Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
  res.redirect(303, snapshot.ref.toString());
  // [END adminSdkPush]
});

Кажется, нам нужно дождаться нескольких обещаний следующим образом:

const listOfAsyncJobs = [];
listOfAsyncJobs.push(createThumbnail(1, ...));
listOfAsyncJobs.push(createThumbnail(2, ...));
listOfAsyncJobs.push(createThumbnail(3, ...));
...
return Promise.all(listOfAsyncJobs); // This will ensure we wait for the end of the three aync tasks above.

Из асинхронного метода все, что вы возвращаете, автоматически оборачивается в обещание. например

const myFun = async () => {return 5}

 myFun();


// Output in the console
Promise {<fulfilled>: 5}

И вы можете связать с возвращенным результатом, так как это обещание

Другой пример с улучшением, предложенным в другом ответе

 const myFun4 = async () => {
      const myNum = await new Promise(r => window.setTimeout(() => r(5), 1000));
      const myNum2 = await new Promise(r => window.setTimeout(() => r(5), 1000));
      return myNum + myNum2;
    }
    myFun4().then((n) => console.info(n));
    // Output
    10

Возвращаемое значение функции async-await - Promise. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#return_value

Итак, на самом деле вы вернули цепочку обещаний.

const nextPromise = () => {
    console.info('next promise!');
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('next promise result')
        }, 3000)
    });
}

const promiseMethod = () => {
    console.info('promise!');
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('promise result');
        }, 2000)
    });
}

exports.createBankAccount = functions.https.onCall((data, context) => {
    return promiseMethod().then((result) => {
        return nextPromise(result);
    }).then((result) => {
        return result;
    }).catch((err) => {
        // handle err
        console.info(err);
    })
});


exports.createBankAccountAsync = functions.https.onCall(async (data, context) => {
    const result = await promiseMethod();
    const res = await nextPromise(result);
    return res;
});

Я создал тестовый проект на firebase, и оба вызова функций дают одинаковые журналы.

Решение в этом случае - Promise.all().

    exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const promises = [];

    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);

    promises.push(res1);
    promises.push(res2);

    // Here's the return of the promises
    return Promise.all(promises).catch(error => console.error(error));
});

Вы можете найти больше информации об обещаниях в этой статье на freecodecamp.org/promise-all

Вы прибили это своим примером кода.

Async / await - это просто новый способ обещания. Их можно использовать взаимозаменяемыми.

Вот пример обещания и async / await одной и той же функции.

Этот

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return promiseMethod().then((result) => {
        return nextPromise(result);
    }).catch((err) => {
        // handle error here
    })
});

эквивалентно этому:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
  try {
    const result = await promiseMethod();
    return nextPromise(result); // You are returning a promise here
  }catch(e) {
    // handle error here
  }
});

Обратите внимание, что в обоих случаях вы возвращаете обещание в конце. Возвращаемое значение этой функции onCall будет тем же, что и nextPromise(result). Поскольку вы возвращаете nextPromsie(result), ждать его не нужно.

Чтобы увидеть кодовое решение вашего вопроса, посмотрите ответ Dshukertjr.

Если вы хотите понять, как вернуть «цепочку обещаний» с помощью async / await, вот ваш ответ:

Вы не можете! Почему ? Потому что await используется для ожидания завершения обещания. После того, как await вернет значение, их больше не будет Promise.

Поэтому, если вы абсолютно хотите вернуть обещание с помощью await, вы можете дождаться одной из двух функций, которые возвращают обещания, но не обоих.

Вот два способа сделать это:

А:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    try {
        const result = await promiseMethod();
        return nextPromise(result); // You are returning a promise here
    }catch(e) {
        // handle error here
    }
});

B:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    return promiseMethod().then(async (result) => {
        return await nextPromise(result);
    }).catch((err) => {
        // handle err
    })
});

Единственное различие между A и B состоит в том, что A ожидает завершения "PromiseMethod", прежде чем вернуть Promise. В то время как B возвращает Promise сразу после вызова.

Поскольку вам нужно вернуть обещание, вы можете создать объект обещания и разрешить / отклонить (вернуть) свой ответ из api после обработки всех обещаний.

Опция 1:

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return new Promise(async (resolve, reject) => {
        try {
            const res1 = await promiseMethod();
            const res2 = await nextPromise(res1);
            // This will return response from api
            resolve(res2);
        }
        catch (err) {
            // Handle error here
            // This will return error from api
            reject(err)
        }
    })
});

Вариант 2:

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return new Promise(async (resolve, reject) => {
        const res1 = await promiseMethod();
        const res2 = await nextPromise(res1);
        // This will return response from api
        resolve(res2);
    })
        .then((val) => val)
        .catch((err) => {
            // Handle error here
            // This will return error from api
            return err
        })
});

К сожалению, функции исполнителя обещаний не должны быть асинхронными.

Daniel ZA 04.04.2021 20:01

Другие вопросы по теме