1

My Google Cloud Function gets an audio file from IBM Watson Text-to-Speech, saves it to Firebase Storage, and gets the signed download URL. Sometimes it will write the download URL to Firestore. Most of the time it won't. Here's the code:

exports.IBM_T2S = functions.firestore.document('Users/{userID}/Spanish/IBM_T2S_Request').onUpdate((change) => {
  if (change.after.data().word != undefined) {
    // get requested word
    let word = change.after.data().word;
    console.log(word);
    let wordFileType = word + '.mp3';

    var synthesizeParams = {
      text: word,
      accept: 'audio/mpeg',
      voice: 'es-LA_SofiaVoice' // "es-ES_LauraVoice", "es-ES_EnriqueVoice", "es-US_SofiaVoice",
    };

    const {Storage} = require('@google-cloud/storage');
    const storage = new Storage();
    const bucket = storage.bucket('myApp.appspot.com');
    const file = bucket.file('Audio/Spanish/Latin_America/' + wordFileType);

    var TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');

    var textToSpeech = new TextToSpeechV1({
      username: 'groucho',
      password: 'swordfish',
      url: 'https://stream.watsonplatform.net/text-to-speech/api'
    });

    const options = { // construct the file to write
      metadata: {
        contentType: 'audio/mpeg',
        metadata: {
          source: 'IBM Watson Text-to-Speech',
          languageCode: 'es-LA',
          gender: 'Female'
        }
      }
    };

    var config = {
      action: 'read',
      expires: '03-17-2025'
    };

    return textToSpeech.synthesize(synthesizeParams).on('error', function(error) {
      console.log(error);
    }).pipe(file.createWriteStream(options))
    .on('error', function(error) {
      console.error(error);
    })
    .on('finish', function() {
      console.log("Audio file written to Storage.");
      // see https://stackoverflow.com/questions/42956250/get-download-url-from-file-uploaded-with-cloud-functions-for-firebase
      file.getSignedUrl({
        action: 'read',
        expires: '03-17-2025'
      })
      .then(function(signedUrls) {
        console.log(signedUrls[0]);
        console.log("Word is now: " + word);

        // Problem is here
        return admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(word).collection('Pronunciations').doc('Latin_America').update({
          audioFile: signedUrls[0]
        })
        .then(function() {
          console.log("Download URL written to database, IBM Latin American, update.");
          return 0;
        })
        .catch(function(error) {
          console.error(error);
          return 0;
        });
        // *****

      })
      .catch(function(error) {
        console.error(error);
        return 0;
      });
    }); // close on
  } else {
    console.error("Error.");
    return 0;
  }
});

In this answer Doug Stevenson said

You're not returning a promise that's resolved when all the async work is complete. If you don't do that, Cloud Functions assumes that all your work is complete, and will clamp down on all resources, and any pending work will be shut down.

The promise returned by translate.translate().then().catch() is being ignored. Your nested call to admin.firestore()...set() has a similar problem. It's not sufficient to just call then() and catch() on every promise because then() and catch() both return yet another promise.

I don't understand what/where to return. The first async call

return textToSpeech.synthesize(synthesizeParams).on('error', function(error) {

has to be returned to prevent the error Function returned undefined, expected Promise or value. I.e., the cloud function finishes executing before the async functions come back, so you need a return before the first async function.

There might be a problem in that IBM Watson returns callbacks, not promises.

I return the last async function, as Doug Stevenson said to:

  return admin.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(word).collection('Pronunciations').doc('Latin_America').update({
          audioFile: signedUrls[0]
        })
        .then(function() {
          console.log("Download URL written to database, IBM Latin American, update.");
          return 0;
        })
        .catch(function(error) {
          console.error(error);
          return 0;
        });

In other words, I return the first async function and the last async function. That doesn't feel right. Is there documentation explaining where returns belong in Google Cloud Functions?

I also put return 0; into the catches, so that the cloud functions closes even when there's an error.

The cloud function usually stops at "Audio file written to Storage." Sometimes it goes on to log the download URL, and sometimes it writes the download URL to Firestore. Why does it sometimes write to Firestore and sometimes not?

Thomas David Kehoe
  • 10,040
  • 14
  • 61
  • 100
  • It looks like you're assuming `on()` returns a promise. Does it? I suspect not. – Doug Stevenson Feb 10 '19 at 19:36
  • Yes, IBM Watson returns callbacks, not promises. I was surprised that the error message "Function returned undefined, expected Promise or value" stopped when I returned the IBM Watson textToSpeech.synthesize function, as it doesn't return a promise or a value (it creates a stream). Your video doesn't cover callbacks. – Thomas David Kehoe Feb 10 '19 at 23:34
  • 1
    Most modern APIs use promises, or let you choose between the two. You can convert a callback to a Promise yourself. – Doug Stevenson Feb 10 '19 at 23:36

0 Answers0