14

I want to use Google Apps Script to make custom functions for a spreadsheet. I've made an extremely simple function:

function foo(){
    return "bar";
};

The problem is that I need this function in a couple hundred cells. When I paste the function =foo() into all of these cells, the function works in a few of the cells, but in most I get this error: "Service invoked too many times: spreadsheet. Try Utilities.sleep(1000) between calls."

[Screenshot here]

I guess I don't understand why this function, simple as it is, is considered an invocation of the Spreadsheet Services. I'm not even requesting any data (except for the function itself). Is that the problem? And if so, is there a workaround? Custom functions could make Google Spreadsheets infinitely more powerful, but this problem hamstrings the possibility of using a custom function in multiple cells. Suggestions?

(P.S. -- Using the Utilities.sleep() function as suggested by the error message doesn't help at all when all of the cells call their functions simultaneously; it only slows the rate at which individual cells repeatedly call the function.)

Rubén
  • 34,714
  • 9
  • 70
  • 166
jrc03c
  • 301
  • 1
  • 2
  • 6

2 Answers2

27

According to the Optimization section on the Apps Script Function Guide:

Each time a custom function is used in a spreadsheet, Google Sheets makes a separate call to the Apps Script server. If your spreadsheet contains dozens (or hundreds, or thousands!) of custom function calls, this process can be quite slow.

Consequently, if you plan to use a custom function multiple times on a large range of data, consider modifying the function so that it accepts a range as input in the form of a two-dimensional array, then returns a two-dimensional array that can overflow into the appropriate cells.

To do this, pass in an input that represents the size of the array you'd like to return. When you start executing your function check if the input parameter is an array with input.map. If it is, you can call the the function on each item and return that entire collection.

So in your case like this:

function foo(){
    return "bar";
};

You can update the function like this:

function foo(input){
  if (input.map) {         // Test whether input is an array.
    return input.map(foo); // Recurse over array if so.
  } else {
    // do actual function work here
    return "bar";
  }
};

And then call it like this:

screenshot

Community
  • 1
  • 1
KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • 2
    If the `foo` function takes multiple arguments but only the first argument may be an array, is there a similar way to recurse over the array too? – Argyll Nov 24 '16 at 17:05
  • this is great, but i'm getting an error if I pass a single value (not cell) to this. it errors out at `if (input.map)`. How do we check before this if the value passed is a cell or an array of cells and not a simple string? – Nikhil VJ Nov 29 '18 at 10:29
  • @NikhilVJ, `input.map` should return truthy if the value is an array; if it's not, the function should evaluate as false and begin work on a single input value / string – KyleMit Nov 29 '18 at 13:22
  • @KyleMit My bad! I was passing string in single quotes instead of double quotes. This works great for both single values and arrays. Thanks! – Nikhil VJ Dec 02 '18 at 03:50
3

By calling the function in the spreadsheet, you are invoking the Spreadsheet service by asking it to go round-trip to the server to run the results of your function. As a result, you have make a couple hundred requests in a very short period of time.

One work around might be to add your function a few cells at a time. Of course, when you subsequently open the sheet again, you will probably run into the same problem.

Depending on what your function is trying to accomplish, it might be worth using the built in spreadsheet functions. There is a lot of power there. Writing a function that acts on a range of values instead of a single cell might be another, better, option. It could be triggered through a custom menu item, or by using the script manager.

Keep in mind, batch actions are your best friend when it comes to working with spreadsheets.

fooby
  • 851
  • 5
  • 6
  • I guess I should've revealed more about my final aim in my question. In short, I've written a function that does a "3D" sum (a sum across sheets). While I know that you can do this with the built-in functions, the problem is that I add sheets on a weekly basis, which means that I have to back in and update those functions to include the new sheets. What I wanted was just a function that iteratively picked up values from ALL the sheets and returned their sum without my having to update the function every week. – jrc03c Dec 20 '12 at 19:30
  • 1
    And I have another question: why is a SERVER necessary for a simple function like the one in my question? Why can't simple things like that be calculated client-side and then pushed into the cells (or at least shown like a HUD over the spreadsheet)? I recognize that this process would cause all kinds of permissions and security questions, but don't Google Apps Scripts already have the ability to ask for permission before fiddling with user data? – jrc03c Dec 21 '12 at 00:58
  • Please provide more details as to the nature of this "3D" sum, or some example code for more assistance. The code you write in google apps script is essentially server-side code. It is not part of the spreadsheet. I'm betting that it acts like a service. Each script is granted its own little database, it has CPU allowances, timeout restrictions etc. There is a lot more going on than just using "`="bar"` as a spreadsheet function. Regardless, I'm sure there is a way to accomplish your sum without hitting these restrictions. – fooby Dec 21 '12 at 12:45
  • A "3D" sum involves adding up values on multiple sheets. For instance, I want to add the A1 cell from Sheet1, Sheet2, and Sheet3. In traditional notation, the formula would be =SUM(Sheet1!A1; Sheet2!A1; Sheet3!A1). But like I said, I add sheets on a weekly basis, which means going back to update this function all the time. Do you think there's a way to do this iteratively (i.e., to grab all the A1 cells from all sheets) without writing a custom function? – jrc03c Dec 21 '12 at 16:29
  • You could add a script that calls `.getFormulas()` on the range with the sums and adds `"; Sheet14!A1"` before the last parantheses to all the formulas and sets them using `.setFormulas(formulas)`. You could even make the script create the sheet and add it to the formulas. It can become part of whatever automation you have every week or this would be a good opportunity to start automating. – Fred Dec 21 '12 at 20:27
  • I'm still not totally clear on your exact use case. I imaging that you are trying to do something like find the sum of weekly hours worked from a time sheet which is kept as a sheet on it's own. You did get me interested in useful functions for summing across sheets in a spreadsheet. So, I made liberal use of the iterative JS Array functions and created the following example for you. You can find it here [Summing Across Sheets](https://docs.google.com/spreadsheet/ccc?key=0Amf6KahEwgTbdGwyQTA5Y2NTR19GdGdTWGFLY0c0dkE) – fooby Dec 22 '12 at 06:40