1

I'm working on a Google Scripts add on for Google Sheets, but I'm trying to get the script working before I actually set it up on the sheet. The code below works fine if I set a breakpoint somewhere in the extractNumbers function. If I just execute the code without breakpoints, I get an error:

TypeError: Cannot call method "replace" of undefined. (line 36, file "")

Here's the code:

var myVar = phoneCheck("a1","a2","o1","o2");
Logger.log(myVar);

function phoneCheck(newCell,newHome,oldCell,oldHome) {
  Logger.clear();
  var newCell = extractNumbers(newCell);
  var oldCell = extractNumbers(oldCell);
  var newHome = extractNumbers(newHome);
  var oldHome = extractNumbers(oldHome);

  if (newCell === oldCell) {
    return newCell;
    exit;
  } else if (newCell === oldHome && newHome === oldCell) {
    return oldCell;
    exit;
  }

  if (newCell === '' && oldCell !== '' ) {
    return oldCell;
    exit;
  }

  if (newCell !== oldCell && newCell !== oldHome) {
   return newCell;
    exit;
  }

  return "No value found";
  exit;
}

function extractNumbers(input) {
  Logger.log(input);
  var str = input;
  return str.replace( /\D+/g, '');
}

Now I realize my if/then logic is more than a bit inelegant, but for my purposes, quick and dirty is fine. I just need it to run.

ALSO, I have read of other novice JavaScript programmers having similar issues related to the sequence of code execution. If someone would like to link to a concise source aimed at a non-advanced audience, that would be great too. Thanks!

EDIT: I put my code into a new fiddle and it works fine, but it continues to fail in Google Scripts editor unless running in debug mode with a breakpoint. The problem seems to be that the function parameters aren't available to the function unless there is a breakpoint. Anyone have access to Google Scripts that can try my updated code from https://jsfiddle.net/hrzqg64L/ ?

Charlie Patton
  • 435
  • 2
  • 12
  • 1
    Usually novice js programmers get tripped up by not understanding the asynchronous nature of AJAX calls. If there's async code in the example you've posted, I'm not seeing it. It might have something to do with how you are shadowing your fn parameters with var declarations, that's a bad idea. – Jared Smith Sep 08 '15 at 17:09
  • I agree with Jared. Remove the var redeclarations from phoneCheck. – patstuart Sep 08 '15 at 17:36
  • Lots of interesting/difficult things going on here. Shadowing params with local vars, calling `exit` (`return` gets you out of the function call by itself), possible async code. – Josh Beam Sep 08 '15 at 17:52
  • Thanks all, great advice. I tried these approaches to resolve problem: 1) Changed the new local var names to a unique name to avoid shadowing. This had no effect. 2) Added extractNumbers to String.prototype and called the function directly on the params within the function. Same deal. 3) Removed the extractNumbers function and instead defined new (uniquely named) variables in the form of `TnewCell = newCell.replace( /\D+/g, '');` Same result. In all cases, adding a breakpoint pretty much anywhere in the script seems to fix the problem. – Charlie Patton Sep 09 '15 at 13:31

2 Answers2

2

None of the suggestions got to the root of your problem - and neither did your answer, although you've avoided the problem by putting an enclosure around everything.

There's no AJAX, no asynchronous behavior - it's simpler than that. "Shadowing of parameters" is likewise a red herring. Bad coding practice, yes - but not a factor here.

If someone would like to link to a concise source aimed at a non-advanced audience, that would be great too.

Sorry - no such thing. I can explain what's going on, but can't guarantee it will be accessible to novices.

The exception

Let's just clarify what causes the exception, or thrown error, that you've observed.

As written, extractNumbers() will throw an exception if it has a null parameter (or any non-string parameter) passed to it. If you choose to extractNumbers() then hit "run", you'll get:

TypeError: Cannot call method "replace" of undefined. (line 36, file "")

That is telling you that on line 36, which is return str.replace( /\D+/g, '');, the variable str contains an object that is undefined (...and has no replace() method).

For bullet-proof code, you would check your parameter(s) to ensure they are valid, and handle them appropriately. Sometimes that would be with a valid default, and other times you might return an error or throw an exception that is more explicit about the parameter problems.

Running code in Google's debugger

The only way to run code in Google's Debugger is to select a function, then choose "run" or "debug". Assuming you posted all your code, you had just two functions to choose from:

  • phoneCheck()
  • extractNumbers()

Whenever Google Apps Script runs any part of a script, the entire script is loaded and scanned to find all symbols & check syntax. The scope of all symbols is noted as well, and so are any dependencies between functions and global symbols (symbols outside of any closure, or block of code).

That takes some time. To speed things up when asked to execute a specific function, the global symbols are only evaluated if they are a dependency for the requested function or the functions it may call. There is another condition that will trigger evaluation of global symbols, and that is if there is a possibility that the debugger may need to stop and display values.

When this happens, any code that is outside a closure (outside a function, for example) will be executed. This is what you observed when you set breakpoints.

Why did it work when breakpoints were set?

As explained, just having a breakpoint set triggers evaluation of global symbols.

You start this script with a few lines of code that are not in any closure:

var myVar = phoneCheck("a1","a2","o1","o2");
Logger.log(myVar);

It is that code which makes the only proper invocation of phoneCheck() with parameters. Because myVar is evaluated, phoneCheck() gets called with parameters, and in turn calls extractNumbers() with a defined parameter.

Unfortunately, because of the way the debugger works, you cannot choose to run that code yourself. You need to rely on these side-effect behaviors.

How to fix this?

Simple. Don't rely on global code to invoke functions under test. Instead, write an explicit test function, and call that.

function test_phoneCheck() {
  var myVar = phoneCheck("a1","a2","o1","o2");
  Logger.log(myVar);
}
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
0

Finally found the issue, but I don't fully understand it.

This question got me thinking about scope and how it might be different in the Google Script environment. I figured a simple workaround would be to enclose the entire script in its own void function, and it worked! Also, I simplified the script quite a bit with an array:

function init () {
  var numberArray = ["a3", "a2", "o3", "o10"];
  var myVar = phoneCheck(numberArray);
  Logger.log(myVar);

  function phoneCheck(myArray) {
    var phoneString = '';
    Logger.clear();
    var arrayLength = myArray.length;
    for (i = 0; i < arrayLength; i++) {
      phoneString += myArray[i].replace(/\D+/g, '');
    }
    return phoneString;
  }
}

Also, I realize the functionality of this script is different than the original, but I was really just trying to solve this problem. Now that I have, I can finish the script properly.

Thanks for all the suggestions, everyone! I learned a lot of good things, even though they turned out not to be the answer.

Community
  • 1
  • 1
Charlie Patton
  • 435
  • 2
  • 12