2
votes

In the app I'm building, users can request a word, and the app goes to the online Oxford Dictionaries API to get the definition, pronunciation, etc. I'm using Firebase Cloud Functions for the HTTP request, and writing the response to Cloud Firestore. Sometimes it works...slowly. Nine out of ten times the data isn't written to Firestore.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request'); // node module to send HTTP requests

admin.initializeApp();

exports.oxford_English_US = functions.firestore.document('Users/{userID}/English_American/Word_Request').onUpdate((change, context) => {
console.log(change.before.data());
console.log(change.after.data());
console.log(context.params.userID);
if (change.after.data().word != undefined) {
  let options = {
    url: 'https://od-api.oxforddictionaries.com/api/v1/entries/en/' + change.after.data().word + '/pronunciations%3Bregions%3Dus',
    headers: {
      "Accept": "application/json",
      'app_id': 'TDK',
      'app_key': 'swordfish'
    }
  };
  function callback(error, response, body) {
    console.log(response.statusCode);
    if (error) {
      console.log(error)
          }
    if (!error && response.statusCode == 200) {
      var word = JSON.parse(body);
      console.log(word);
      admin.firestore().collection('Users').doc(context.params.userID).collection('English_American').doc('Word_Response').set({
        'metadata': word.metadata,
        'results': word.results
      })
       .then(function() {
        console.log("Document written.");
      })
      .catch(function(error) {
        console.log("Error writing document: ", error);
      })
    }
  }
  request(options, callback);
} else {
  console.log("change.after.data().word === undefined");
}
return 0;

});

Here's the log for one function call:

12:55:12.780 PM oxford_English_US { metadata: { provider: 'Oxford University Press' }, results: [ { id: 'to', language: 'en', lexicalEntries: [Object], type: 'headword', word: 'to' } ] }
12:55:12.480 PM oxford_English_US 200
12:55:09.813 PM oxford_English_US Function execution took 3888 ms, finished with status: 'ok'
12:55:09.739 PM oxford_English_US bcmrZDO0X5N6kB38MqhUJZ11OzA3
12:55:09.739 PM oxford_English_US { word: 'to' }
12:55:09.732 PM oxford_English_US { word: 'have' }
12:55:05.926 PM oxford_English_US Function execution started

The function executed in four seconds. Three seconds after the function finishes executing, the "200" status code comes back, with the data. The data is never written to Firestore.

It looks like the Cloud Function doesn't wait for the HTTP response.

Here's another function log, in which the Cloud Function appears to have finished executing before sending out the HTTP request:

  1:02:29.319 PM oxford_English_US Function execution took 3954 ms, finished with status: 'ok'
  1:02:29.218 PM oxford_English_US bcmrZDO0X5N6kB38MqhUJZ11OzA3
  1:02:29.218 PM oxford_English_US { word: 'to' }
  1:02:29.213 PM oxford_English_US { word: 'the' }
  1:02:25.365 PM oxford_English_US Function execution started

Is there a better Node module for sending HTTP requests? request has this syntax:

request(options, callback);

I'd rather return a promise instead of a callback.

2

2 Answers

4
votes

Your Google Cloud Function should always return a Promise. If you fail to return a Promise, you'll face a condition in which the application container might be torn down before your code completes.

From your example code:

exports.oxford_English_US = functions.firestore.document('Users/{userID}/English_American/Word_Request').onUpdate((change, context) => {
 
  return new Promise(function(resolve, reject) {
      // put your code here and resolve() or reject() based on outcome
  });

}

From the Firebase YouTube channel (2. Background Triggers - return a promise) "You must return a Promise that becomes fulfilled or rejected when the pending work in that function is complete. This lets Cloud Functions know when it's safe to clean up the function invocation and move on to the next one."

https://youtu.be/652XeeKNHSk?t=44s

0
votes

I changed the Node module request to request-promise. It's executing reliably and fast now. Here's my new code:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const rp = require('request-promise');

admin.initializeApp();

exports.oxford_English_US = functions.firestore.document('Users/{userID}/English_American/Word_Request').onUpdate((change, context) => {
  if (change.after.data().word != undefined) {
    let options = {
      uri: 'https://od-api.oxforddictionaries.com/api/v1/entries/en/' + change.after.data().word + '/pronunciations%3Bregions%3Dus',
      headers: {
        "Accept": "application/json",
        'app_id': 'TDK',
        'app_key': 'swordfish'
      },
      json: true
    };

    rp(options)
    .then(function (word) {
      admin.firestore().collection('Users').doc(context.params.userID).collection('English_American').doc('Word_Response').set({
        'metadata': word.metadata,
        'results': word.results
      })
      .then(function() {
        console.log("Document written.");
      })
      .catch(function(error) {
        console.log("Error writing document: ", error);
      })
    })
    .catch(function (error) {
      console.log(error);
    })
  } else {
    console.log("change.after.data().word === undefined");
  }
  return 0;
});