Including Async Function Within Firebase Cloud Functions (eslint "Parsing error: Unexpected token function")
Asked Answered
I

8

23

Issue

How can an async helper method be added to a Cloud Functions' index.js file? An async function is required in order to use await when converting fs.writefile into a Promise as explained in this StackOverflow post: fs.writeFile in a promise, asynchronous-synchronous stuff. However, lint does not approve of adding an additional method outside of the exports functions to the index.js file.

Error

Line 84 refers to the helper function async function writeFile.

Users/adamhurwitz/coinverse/coinverse-cloud-functions/functions/index.js 84:7 error Parsing error: Unexpected token function

✖ 1 problem (1 error, 0 warnings)

npm ERR! code ELIFECYCLE

npm ERR! errno 1

npm ERR! functions@ lint: eslint .

npm ERR! Exit status 1

npm ERR!

npm ERR! Failed at the functions@ lint script.

npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:

npm ERR! /Users/adamhurwitz/.npm/_logs/2018-12-12T01_47_50_684Z-debug.log

Error: functions predeploy error: Command terminated with non-zero exit code1

Setup

index.js

const path = require('path');
const os = require('os');
const fs = require('fs');
const fsPromises = require('fs').promises;
const util = require('util');
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const {Storage} = require('@google-cloud/storage');
const textToSpeech = require('@google-cloud/text-to-speech');

const storage = new Storage({
  projectId: 'project-id',
});
const client = new textToSpeech.TextToSpeechClient();

admin.initializeApp();

exports.getAudiocast = functions.https.onCall((data, context) => {
  const bucket = storage.bucket('gs://[bucket-name].appspot.com');
  var fileName;
  var tempFile;
  var filePath;

  return client.synthesizeSpeech({
    input: {text: data.text },
    voice: {languageCode: 'en-US', ssmlGender: 'NEUTRAL'},
    audioConfig: {audioEncoding: 'MP3'},
  })
  .then(responses => {
    var response = responses[0]; 
    fileName = data.id + '.mp3'
    tempFile = path.join(os.tmpdir(), fileName);  
    return writeFile(tempFile, response.audioContent)
  })
  .catch(err => {
    console.error("Synthesize Speech Error: " + err);
  })
  .then(() => {
     filePath = "filePath/" + fileName;
     return bucket.upload(tempFile, { destination: filePath })
  })
  .catch(err => {
     console.error("Write Temporary Audio File Error: " + err);
  })
  .then(() => {
   return { filePath: filePath }
  })
  .catch(err => {
     console.error('Upload Audio to GCS ERROR: ' + err);
  });
});

Helper method:

async function writeFile(tempFile, audioContent) {
    await fs.writeFile(tempFile, audioContent, 'binary');
}

Attempted Solution

Enabling Node.js 8 as recommended in the post Cloud Functions for Firebase Async Await style.

  1. Set Node.js version "engines": {"node": "8"}

  2. return await fs.writeFile(tempFile, audioContent, 'binary');

Lint does not like this solution.

Irrefragable answered 12/12, 2018 at 1:59 Comment(0)
I
2

Node.js 8 - Promisify

Enabling Node.js 8 as recommended in the post Cloud Functions for Firebase Async Await style.

  1. Set Node.js version "engines": {"node": "8"}
  2. Use promisify

    const writeFile = util.promisify(fs.writeFile);

    return writeFile(tempFile, response.audioContent, 'binary')

Pre Node.js 8 - Manual Conversion

This is an older approach to convert Callbacks to Promises as outlined by this answer regarding a more specific question about Google Text To Speech (TTS).

const writeFilePromise = (file, data, option) => {
   return new Promise((resolve, reject) => {
       fs.writeFile(file, data, option, error => {
          if (error) reject(error);
          resolve("File created! Time for the next step!");
       });
   });
};

return writeFilePromise(tempFile, response.audioContent, 'binary');
Irrefragable answered 12/12, 2018 at 2:10 Comment(1)
"engines": {"node": "8"} was the key for me, thank you so muchTervalent
G
156

I tried all solutions above which did not work for me. It was due to bad syntax in my package.json :

"scripts": {
    "lint": "eslint ."
  },

changed to :

"scripts": {
    "lint": "eslint"
  },

Like said Burak in the comments, this dot is put by default when we create firebase functions

Grassgreen answered 16/1, 2021 at 23:42 Comment(3)
I'm not sure that this solves the problem. From my experience it makes eslint work against the wrong target directory. Try to have bad indentation and it won't be flagged anymoreRemember
BE CAREFUL with this. Maybe I'm missing something, but for me this "fix" turned off lint warnings completely.Biped
THIS IS NOT A FIX It does not make eslint "work" -- it makes eslint not actually scan your code. You may as well delete the entire lint script if you're going to do this.Taub
M
20

Your eslint is not configured to understand ECMAScript 2017 syntax. The .eslint.json config file that's created by the Fireabse CLI by default includes this configuration:

"parserOptions": {
  // Required for certain syntax usages
  "ecmaVersion": 6
},

Change it like this to help it understand async/await:

  "ecmaVersion": 2017
Muzhik answered 12/12, 2018 at 3:49 Comment(4)
I've added "ecmaVersion": 2017 to the eslintrc.json file in the project, however Lint is still throwing an error when returning a promise generated from the await: return await fs.writeFile(tempFile, responses[0].audioContent, 'binary');Irrefragable
That sounds like a different problem.Muzhik
The only difference is replacing the promisify with the await. Otherwise, the writeFile method works as expected when using promisify so I can move forward with that approach.Irrefragable
True, the most recent versions adopt the format as es2017: true in the env sectionMostly
V
15

ISSUE ES7 you have to change it to ES8

  • ES7 released on 2016 does not have async,await nor arrow functions
  • ES8 released on 2017 have have async,await and arrow functions

you have to check on your .eslintrc that you have at least es8 or 2017 which is the same.

if the file is .eslintrc.json

"ecmaVersion": 2017 or "ecmaVersion": 8

if the file is .eslintrc.js

env:{ es8:true, node:true }

for some it works that way

In my case it solved by changing package.json

"scripts": { "lint": "eslint ." },

changed to :

"scripts": { "lint": "eslint" },

as Jonathan Said but I wonder why?

and a realized that I had two files with the name

  • .eslintrc.js
  • .eslintrc.json

this is eslintrc.json

and

this is eslintrc.json

as you can see there are diferent versions of ecmaScript in the two files with the same name,

  • "ecmaVersion": 2017 // which is equal to es8 in file: .eslintrc.json
  • es6: true, // which was released on June 2015 in file: .eslintrc.js

so when we run : npm run lint it runs the .eslintrc.js with es6:true so to solve this conflict was just to delete .eslintrc.js because it has the wrong ecmaScript.

Veron answered 23/6, 2021 at 19:30 Comment(1)
Thanks a lot. The difference between js and json file has helped. I wish I can double upvote this answer.Koal
B
14

If you have .eslint.js - just change es6: true to es2017: true.

For example:

  env: {
    es6: true,
    node: true,
  },

becomes:

  env: {
    es2017: true,
    node: true,
  },

More details in ESLint language options doc.

Biped answered 15/11, 2021 at 19:24 Comment(1)
This solved my issue exactly.Premises
I
2

Node.js 8 - Promisify

Enabling Node.js 8 as recommended in the post Cloud Functions for Firebase Async Await style.

  1. Set Node.js version "engines": {"node": "8"}
  2. Use promisify

    const writeFile = util.promisify(fs.writeFile);

    return writeFile(tempFile, response.audioContent, 'binary')

Pre Node.js 8 - Manual Conversion

This is an older approach to convert Callbacks to Promises as outlined by this answer regarding a more specific question about Google Text To Speech (TTS).

const writeFilePromise = (file, data, option) => {
   return new Promise((resolve, reject) => {
       fs.writeFile(file, data, option, error => {
          if (error) reject(error);
          resolve("File created! Time for the next step!");
       });
   });
};

return writeFilePromise(tempFile, response.audioContent, 'binary');
Irrefragable answered 12/12, 2018 at 2:10 Comment(1)
"engines": {"node": "8"} was the key for me, thank you so muchTervalent
O
0

change ecmaVersion in .eslintrc.json

"parserOptions": {
// Required for certain syntax usages
"ecmaVersion": 8
}

Orthopterous answered 11/7, 2019 at 11:56 Comment(0)
R
0

I ran into the same issue.
I used the same ESLint configuration as used in one of the functions in the Firebase functions samples GitHub repo. I referred to this particular example here: https://github.com/firebase/functions-samples/tree/main/child-count, which makes use of async functions.
I removed the eslintrc.js and replaced it with the same eslintrc.json file as in the above sample. Also, in package.json, added a new dependency "eslint-plugin-promise": "^4.3.1", and removed the default "eslint-config-google": "^0.14.0" (Again, as is in the above sample).

This seems to have fixed it for me.

Robbie answered 8/12, 2022 at 12:25 Comment(0)
C
-1

.eslint.json

"parserOptions": { // Required for certain syntax usages "ecmaVersion": 6 }, Change it like this to help it understand async/await:

"ecmaVersion": 2017

package.json "scripts": { "lint": "eslint ." }, changed to :

"scripts": { "lint": "eslint" },

Refereces Captain Web and Doug Stevenson

Consol answered 15/2, 2021 at 5:40 Comment(1)
Please have a read of the formatting help page to improve the formatting in your answer, and also check out How do I write a good answer? to improve your answer.Paisano

© 2022 - 2025 — McMap. All rights reserved.