Help

SyntaxError in scripting app: Unexpected identifier 'input' on line 1 - but line 1 is a comment

Topic Labels: Extensions
Solved
Jump to Solution
4452 7
cancel
Showing results for 
Search instead for 
Did you mean: 
Emar_Vegt
5 - Automation Enthusiast
5 - Automation Enthusiast

Hi all,
I’m diving into the wonderful world of Airtable scripting with a custom CSV import script that does some data interpretation and creates links to other tables. Starting point was the CSV import example.
I managed to get that working and added more functionality.

Now, I keep getting an error that I cannot seem to solve:
Screenshot 2020-12-02 at 16.12.13

But the editor shows no red markings, and the first line of my script is a comment!
Here are the first few lines of my script:

// Timesheet CSV importer by Emar Vegt
output.text("Note: this script does not handle duplicates yet. You'll have to sort them out yourself.");

// Ask the user to import a CSV file containing a header row
let csvFileResult = await input.fileAsync(
    'Upload a timesheet CSV file',
    {allowedFileTypes: ['.csv'], hasHeaderRow: true}
);

// The file importer will automatically parse contents for many file types, including CSV files
let csv_rows = csvFileResult.parsedContents;

// Edit this to the name of a table in your base
let timesheet_table = base.getTable('Timesheets');
let person_table = base.getTable('Design team (sync)');
let month_table = base.getTable('Months');
let i_length = csv_rows.length;
let script_progress = 0;
let debug = false;

let should_continue = await input.buttonsAsync(
    `Import ${csv_rows.length} records from \"${csvFileResult.file.name}\" into         ${timesheet_table.name}?`,
    [{label: 'Yes', variant: 'primary'}, 'No', 'Test only']
);

if (should_continue == 'Test only') {
    debug = true;
    should_continue = 'Yes';
}

if (should_continue === 'Yes') {

Anyone have an idea? Copy-pasting the entire code into a new scripting block also didn’t help.
I have a fair understanding of javascript (and Googling for errors, documentation and ways to achieve what I want) but this one got me stuck.

1 Solution

Accepted Solutions
Emar_Vegt
5 - Automation Enthusiast
5 - Automation Enthusiast

The error happens before anything is displayed in the output window. So it means the error is actually triggered on or before line 2 of my script, as line 2 would output a string to the window.

In my mind, that rules out any other part of my code, right? How can it have to do with the additional functionality or the CSV file if the script does not even get to that part?

I’ll try deconstructing my code further.

Edit:
I started chopping the code with block comments like Justin suggested and found the culprit:

Apparently you can’t have an input.buttonsAsync() function inside of a map() function.

I was doing some data processing with functions inside of map(), which works fine, but in those you cannot ask for user input – that triggered this nondescript error right from the start of the script. Removing the input.buttonsAsync() from inside map() solved it.

So for reference, an excerpt of what works:

let new_records = csv_rows.map(csv_row => ({
    fields: {
        'Employee Name': csv_row['Employee Name'],
        'Period end date': format_date(csv_row['Period end date']),
    }
}));
// and then write new_records to your table as per the CSV import example.

Somewhere else in your code you can declare the format_date() function and what it does (returning a formatted date in my case). But within format_date(), you can’t use input.buttonsAsync().

See Solution in Thread

7 Replies 7

Since the script was working before you added more functionality, what code did you add?

When in the execution of the script does the error occur? Before you upload the file? After uploading the file, but before showing the button? After the user clicks a button?

Have you checked the format of the CSV file that you are importing?

It is also possible that the error is occurring in the code that parses the file, and not your code.

That’s been the case from my experience. Whenever I see a really long error message like that where all errors are on “line 1”, it’s never line 1 of my own code. It generally means that something in the script parser on Airtable’s side of things is encountering an error. It’s still related to your code, but it just makes troubleshooting much more difficult.

I suggest testing the code in sections. Use the block comment option to block out most of the end of the code, leaving only a small portion at the top un-commented. Once you know that works, move the start of the block comment to expose a few more lines of your code, and test the script again. Lather, rinse, and repeat until you’ve found the part of the code that triggers the error.

If you’re not familiar with block comments, here’s a quick rundown.

// This is a single-line comment

/*
This is a block comment
All code until the end of the block comment will be ignored by the interpreter
Including literal code like this
let something = "Something";
The end of the block comment has the slash-asterisk in reverse order, like this...
*/

If I were using this to test some code, it might look like this for the first test:

let tb = base.getTable("Table");
/*
let query = await tb.selectRecordsAsync();
let records = query.records;
*/

Move the start of the block comment a line or two down for the next test, depending on how your code is designed:

let tb = base.getTable("Table");
let query = await tb.selectRecordsAsync();
/*
let records = query.records;
*/

And so on until you’ve isolated the part of the code causing the error.

Emar_Vegt
5 - Automation Enthusiast
5 - Automation Enthusiast

The error happens before anything is displayed in the output window. So it means the error is actually triggered on or before line 2 of my script, as line 2 would output a string to the window.

In my mind, that rules out any other part of my code, right? How can it have to do with the additional functionality or the CSV file if the script does not even get to that part?

I’ll try deconstructing my code further.

Edit:
I started chopping the code with block comments like Justin suggested and found the culprit:

Apparently you can’t have an input.buttonsAsync() function inside of a map() function.

I was doing some data processing with functions inside of map(), which works fine, but in those you cannot ask for user input – that triggered this nondescript error right from the start of the script. Removing the input.buttonsAsync() from inside map() solved it.

So for reference, an excerpt of what works:

let new_records = csv_rows.map(csv_row => ({
    fields: {
        'Employee Name': csv_row['Employee Name'],
        'Period end date': format_date(csv_row['Period end date']),
    }
}));
// and then write new_records to your table as per the CSV import example.

Somewhere else in your code you can declare the format_date() function and what it does (returning a formatted date in my case). But within format_date(), you can’t use input.buttonsAsync().

Thank you for coming back and explaining the problem. It is easier to understand the error now.

The reason the error happened before anything was displayed is because it was a syntax error. The grammar of the code was wrong, which prevented any of the script from running.

The error is for “Function” without a name because the function inside your map is an anonymous function without a name. The error is at “line 1” because the error was at the first line of the anonymous function. The error is a problem with a promise, because the input functions return promises.

I really appreciate you coming back with your solution because it has helped me improve my own coding and debugging skills.

Not necessarily. Remember that you’re required to use the await keyword before calling asynchronous functions/methods like input.buttonsAsync(). If you use that asynchronous call inside another function, that function also must be defined as asynchronous. In other words, if you define format_date as an asynchronous function…

async function format_date(data) {
    // code here, including await input.buttonsAsync()
}

…your example should work if you call that function using await, like this:

let new_records = csv_rows.map(csv_row => ({
    fields: {
        'Employee Name': csv_row['Employee Name'],
        'Period end date': await format_date(csv_row['Period end date']),
    }
}));
// and then write new_records to your table as per the CSV import example.

Admittedly I haven’t tested this (and don’t have time to fully do so right now), but I think it should work.

Emar_Vegt
5 - Automation Enthusiast
5 - Automation Enthusiast

I tried the suggestion by Justin.
The compiler does not like the await format_date() inside map():
SyntaxError: Unexpected identifier 'format_date'. Expected '}' to end an object literal.

If I remove await there but keep the

async function format_date(data) {
    await input.buttonsAsync(); // and more
} 

the script does not wait for my button input but runs right through.

And it seems calling an async function inside map() also throws an error:

ERROR
L: Can't create records: invalid cell value for field 'Hours Clocked'.
Cell value has invalid format: <root> must be a number
    at spawnErrorWithOriginOmittedFromStackTrace on line 1
    at _assertMutationIsValid on line 1
    at applyMutationAsync$ on line 1
    at tryCatch on line 1
    at invoke on line 1
    at tryCatch on line 1
    at invoke on line 1
    on line 1
    at Promise
    at callInvokeWithMethodAndArg on line 1
    at enqueue on line 1
    on line 1
    at createRecordsAsync$ on line 1
    at tryCatch on line 1
    at invoke on line 1
    at tryCatch on line 1
    at invoke on line 1
    on line 1
    at Promise
    at callInvokeWithMethodAndArg on line 1
    at enqueue on line 1
    on line 1
    at tryCatch on line 1
    at invoke on line 1
    at asyncGeneratorStep on line 1

For now, I will remove the button interaction here and continue with my other issue, namely that the automatic CSV interpreter can’t handle commas as decimal notation (4,5 becomes 45)…
The new Excel file support does a better job with that when using the built-in import function for a base, but there is no documentation about the differences between loading an Excel file in the scripting app vs loading a CSV other than the statement that ‘.xlsx’ files are supported. Simply replacing the file type causes other errors during the interpretation stage.

Good to know. I just tried a more detailed test, which pointed out what I believe is the core issue: the map method has no mechanism for calling an asynchronous function. It assumes that any function passed—anonymous or named—is synchronous, and there’s no way to override that assumption and tell it to wait for an asynchronous function’s return for each item.

That said, you could still get what you want by doing the manual version of what map does automatically. Make format_date an asynchronous function as described above, then use this:

let new_records = [];
for (let csv_row of csv_rows) {
    let end_date = await format_date(csv_row['Period end date']);
    new_records.push({
        fields: {
            'Employee Name': csv_row['Employee Name'],
            'Period end date': end_date
        }
    });
}
// and then write new_records to your table as per the CSV import example.

I ran a test similar to this, and it waited for the button click for each item in my original array.