Перенос сценариев в среду выполнения V8

Поддержка среды выполнения Rhino прекратится 31 января 2026 года или позже. Если у вас есть существующий скрипт, использующий среду выполнения Rhino, вам необходимо перевести его на версию V8.

Зачастую единственным предварительным условием для добавления синтаксиса и функций V8 в скрипт является включение среды выполнения V8 . Однако существует небольшой набор несовместимостей и других различий , которые могут привести к сбою скрипта или его непредсказуемому поведению в среде выполнения V8. При миграции скрипта на использование V8 необходимо проверить проект скрипта на наличие этих проблем и исправить все обнаруженные.

процедура миграции V8

Для переноса скрипта на V8 выполните следующие действия:

  1. Включите среду выполнения V8 для скрипта. runtimeVersion можно проверить с помощью манифеста проекта Google Apps Script.
  2. Внимательно проверьте следующие несовместимости . Изучите свой скрипт, чтобы определить, присутствуют ли какие-либо из этих несовместимостей; если присутствует одна или несколько несовместимостей, скорректируйте код скрипта, чтобы устранить или избежать проблемы.
  3. Внимательно изучите следующие отличия . Проанализируйте свой скрипт, чтобы определить, влияют ли какие-либо из перечисленных отличий на поведение вашего кода. Внесите изменения в скрипт для исправления поведения.
  4. После устранения обнаруженных несовместимостей или других различий начните обновлять свой код, чтобы использовать синтаксис V8 и другие возможности .
  5. После завершения внесения изменений в код, тщательно протестируйте свой скрипт, чтобы убедиться, что он работает должным образом.
  6. Если ваш скрипт представляет собой веб-приложение или опубликованное дополнение , необходимо создать новую версию скрипта с изменениями, внесенными в V8, и указать при развертывании путь к новой созданной версии. Чтобы сделать версию V8 доступной для пользователей, необходимо повторно опубликовать скрипт с этой версией.
  7. Если ваш скрипт используется как библиотека, создайте новую версию развертывания скрипта. Сообщите об этой новой версии всем скриптам и пользователям, которые используют вашу библиотеку, попросив их обновиться до версии с поддержкой V8. Убедитесь, что все старые версии вашей библиотеки, основанные на Rhino, больше не используются или недоступны.
  8. Убедитесь, что ни один из экземпляров вашего скрипта больше не работает на устаревшей среде выполнения Rhino. Убедитесь, что все развертывания связаны с версией, использующей V8. Заархивируйте старые развертывания. Просмотрите все версии и удалите те, которые не используют среду выполнения V8.

Несовместимости

К сожалению, исходная среда выполнения Apps Script на базе Rhino допускала ряд нестандартных действий ECMAScript. Поскольку V8 соответствует стандартам, эти действия не поддерживаются после миграции. Неисправление этих проблем приводит к ошибкам или некорректной работе скрипта после включения среды выполнения V8.

В следующих разделах описаны все эти варианты поведения и шаги, которые необходимо предпринять для исправления кода скрипта во время миграции на V8.

Избегайте for each(variable in object)

В JavaScript 1.6 был добавлен оператор for each (variable in object) , который затем был удалён в пользу ` for...of .`.

При миграции вашего скрипта на V8 избегайте использования операторов for each (variable in object) .

Вместо этого используйте for (variable in object) :

// Rhino runtime
var obj = {a: 1, b: 2, c: 3};

// Don't use 'for each' in V8
for each (var value in obj) {
  Logger.log("value = %s", value);
}
      
// V8 runtime
var obj = {a: 1, b: 2, c: 3};

for (var key in obj) {  // OK in V8
  var value = obj[key];
  Logger.log("value = %s", value);
}
      

Избегайте использования Date.prototype.getYear()

В оригинальной среде выполнения Rhino метод Date.prototype.getYear() возвращает двузначные годы для периодов с 1900 по 1999 год, но четырехзначные годы для других дат, что соответствовало поведению JavaScript 1.2 и более ранних версий.

В среде выполнения V8 метод Date.prototype.getYear() возвращает год минус 1900, как того требуют стандарты ECMAScript.

При переносе вашего скрипта на V8 всегда используйте Date.prototype.getFullYear() , который возвращает четырехзначный год независимо от даты.

Избегайте использования зарезервированных ключевых слов в качестве названий.

ECMAScript запрещает использование некоторых зарезервированных ключевых слов в именах функций и переменных. Среда выполнения Rhino допускала использование многих из этих слов, поэтому, если ваш код их использует, вам необходимо переименовать ваши функции или переменные.

При переносе вашего скрипта на V8 избегайте именования переменных или функций с использованием зарезервированных ключевых слов . Переименуйте любую переменную или функцию, чтобы избежать использования имени ключевого слова. Часто в качестве имен ключевых слов используются, например, class , import и export .

Исключением является то, что в литералах объектов разрешено использовать зарезервированные ключевые слова (во всех средах выполнения):

function class() {}     // Syntax error in V8.
var obj = { class: 1 }; // Allowed.

Избегайте переназначения const переменных.

В оригинальной среде выполнения Rhino можно объявить переменную с помощью const , что означает, что значение символа никогда не изменяется, и будущие присваивания этому символу игнорируются.

В новой среде выполнения V8 ключевое слово const соответствует стандарту, и присваивание значения переменной, объявленной как const приводит к ошибке выполнения TypeError: Assignment to constant variable .

При переносе вашего скрипта на V8 не пытайтесь переназначать значение const переменной :

// Rhino runtime
const x = 1;
x = 2;          // No error
console.log(x); // Outputs 1
      
// V8 runtime
const x = 1;
x = 2;          // Throws TypeError
console.log(x); // Never executed
      

Избегайте использования XML-литералов и XML-объектов.

Это нестандартное расширение ECMAScript позволяет проектам Apps Script напрямую использовать синтаксис XML.

При миграции вашего скрипта на V8 избегайте использования прямых XML-литералов или XML-объектов .

Вместо этого используйте XmlService для анализа XML:

// V8 runtime
var incompatibleXml1 = <container><item/></container>;             // Don't use
var incompatibleXml2 = new XML('<container><item/></container>');  // Don't use

var xml3 = XmlService.parse('<container><item/></container>');     // OK
      

Не создавайте собственные функции итераторов, используя __iterator__

В JavaScript 1.7 была добавлена ​​функция, позволяющая добавлять пользовательский итератор к любому классу путем объявления функции ` __iterator__ в прототипе этого класса; эта функция также была добавлена ​​в среду выполнения Rhino в Apps Script для удобства разработчиков. Однако эта функция никогда не была частью стандарта ECMA-262 и была удалена в JavaScript-движках, совместимых с ECMAScript. Скрипты, использующие V8, не могут использовать эту конструкцию итератора.

При миграции вашего скрипта на V8 избегайте использования функции __iterator__ для создания пользовательских итераторов . Вместо этого используйте итераторы ECMAScript 6 .

Рассмотрим следующую конструкцию массива:

// Create a sample array
var myArray = ['a', 'b', 'c'];
// Add a property to the array
myArray.foo = 'bar';

// The default behavior for an array is to return keys of all properties,
//  including 'foo'.
Logger.log("Normal for...in loop:");
for (var item in myArray) {
  Logger.log(item);            // Logs 0, 1, 2, foo
}

// To only log the array values with `for..in`, a custom iterator can be used.
      

Приведенные ниже примеры кода показывают, как можно создать итератор в среде выполнения Rhino и как создать заменяющий итератор в среде выполнения V8:

// Rhino runtime custom iterator
function ArrayIterator(array) {
  this.array = array;
  this.currentIndex = 0;
}

ArrayIterator.prototype.next = function() {
  if (this.currentIndex
      >= this.array.length) {
    throw StopIteration;
  }
  return "[" + this.currentIndex
    + "]=" + this.array[this.currentIndex++];
};

// Direct myArray to use the custom iterator
myArray.__iterator__ = function() {
  return new ArrayIterator(this);
}


Logger.log("With custom Rhino iterator:");
for (var item in myArray) {
  // Logs [0]=a, [1]=b, [2]=c
  Logger.log(item);
}
      
// V8 runtime (ECMAScript 6) custom iterator
myArray[Symbol.iterator] = function() {
  var currentIndex = 0;
  var array = this;

  return {
    next: function() {
      if (currentIndex < array.length) {
        return {
          value: "[${currentIndex}]="
            + array[currentIndex++],
          done: false};
      } else {
        return {done: true};
      }
    }
  };
}

Logger.log("With V8 custom iterator:");
// Must use for...of since
//   for...in doesn't expect an iterable.
for (var item of myArray) {
  // Logs [0]=a, [1]=b, [2]=c
  Logger.log(item);
}
      

В среде выполнения V8 необходимо использовать for...of при обходе массивов с пользовательскими итераторами, поскольку for..in не ожидает итерируемых объектов.

Избегайте условных оговорок.

Среда выполнения V8 не поддерживает условные конструкции catch..if , поскольку они не соответствуют стандарту.

При миграции вашего скрипта на V8 переместите все условные операторы catch внутрь тела блока catch :

// Rhino runtime

try {
  doSomething();
} catch (e if e instanceof TypeError) {  // Don't use
  // Handle exception
}
      
// V8 runtime
try {
  doSomething();
} catch (e) {
  if (e instanceof TypeError) {
    // Handle exception
  }
}

Избегайте использования Object.prototype.toSource()

В JavaScript 1.3 присутствовал метод Object.prototype.toSource() , который никогда не входил в стандарт ECMAScript. Он не поддерживается в среде выполнения V8.

При миграции вашего скрипта на V8 удалите все случаи использования Object.prototype.toSource() из вашего кода.

Другие различия

Помимо упомянутых выше несовместимостей, которые могут привести к сбоям в работе скриптов, существует еще несколько различий, которые, если их не устранить, могут привести к неожиданному поведению скриптов во время выполнения V8.

В следующих разделах объясняется, как обновить код скрипта, чтобы избежать этих неожиданных сюрпризов.

Настройте форматирование даты и времени в соответствии с вашими региональными особенностями.

Методы Date toLocaleString() , toLocaleDateString() и toLocaleTimeString() ведут себя по-разному в среде выполнения V8 по сравнению с Rhino.

В Rhino по умолчанию используется длинный формат , а любые переданные параметры игнорируются .

В среде выполнения V8 по умолчанию используется короткий формат , а передаваемые параметры обрабатываются в соответствии со стандартом ECMA (подробнее см. документацию по функции toLocaleDateString() ).

При переносе вашего скрипта на V8 протестируйте и скорректируйте ожидаемые значения для методов работы с датой и временем, специфичных для вашей локали :

// Rhino runtime
var event = new Date(
  Date.UTC(2012, 11, 21, 12));

// Outputs "December 21, 2012" in Rhino
console.log(event.toLocaleDateString());

// Also outputs "December 21, 2012",
//  ignoring the parameters passed in.
console.log(event.toLocaleDateString(
    'de-DE',
    { year: 'numeric',
      month: 'long',
      day: 'numeric' }));
// V8 runtime
var event = new Date(
  Date.UTC(2012, 11, 21, 12));

// Outputs "12/21/2012" in V8
console.log(event.toLocaleDateString());

// Outputs "21. Dezember 2012"
console.log(event.toLocaleDateString(
    'de-DE',
    { year: 'numeric',
      month: 'long',
      day: 'numeric' }));
      

Избегайте использования Error.fileName и Error.lineNumber

В среде выполнения V8 стандартный объект JavaScript Error не поддерживает fileName или lineNumber в качестве параметров конструктора или свойств объекта.

При переносе вашего скрипта на V8 удалите все зависимости от Error.fileName и Error.lineNumber .

Альтернативным вариантом является использование Error.prototype.stack . Этот стек также нестандартен, но поддерживается в V8. Формат трассировки стека, создаваемый двумя платформами, немного отличается:

// Rhino runtime Error.prototype.stack
// stack trace format
at filename:92 (innerFunction)
at filename:97 (outerFunction)
// V8 runtime Error.prototype.stack
// stack trace format
Error: error message
at innerFunction (filename:92:11)
at outerFunction (filename:97:5)
      

Корректировка обработки строковых объектов перечисления.

В оригинальной среде выполнения Rhino использование метода JavaScript JSON.stringify() для объекта перечисления возвращает только {} .

В версии V8 использование того же метода для объекта перечисления возвращает имя перечисления.

При переносе вашего скрипта на V8 протестируйте и скорректируйте ожидаемые результаты выполнения метода JSON.stringify() для объектов перечисления :

// Rhino runtime
var enumName =
  JSON.stringify(Charts.ChartType.BUBBLE);

// enumName evaluates to {}
// V8 runtime
var enumName =
  JSON.stringify(Charts.ChartType.BUBBLE);

// enumName evaluates to "BUBBLE"

Настроить обработку неопределенных параметров.

В оригинальной среде выполнения Rhino передача параметра " undefined " методу приводила к передаче в этот метод строки "undefined" .

В V8 передача undefined методам эквивалентна передаче null .

При переносе вашего скрипта на V8 протестируйте и скорректируйте ожидания вашего кода в отношении undefined параметров :

// Rhino runtime
SpreadsheetApp.getActiveRange()
    .setValue(undefined);

// The active range now has the string
// "undefined"  as its value.
      
// V8 runtime
SpreadsheetApp.getActiveRange()
    .setValue(undefined);

// The active range now has no content, as
// setValue(null) removes content from
// ranges.

Скорректировать обработку глобального this

Среда выполнения Rhino определяет неявный специальный контекст для скриптов, которые его используют. Код скрипта выполняется в этом неявном контексте, отличном от фактического глобального this . Это означает, что ссылки на «глобальный this » в коде фактически относятся к специальному контексту, который содержит только код и переменные, определенные в скрипте. Встроенные сервисы Apps Script и объекты ECMAScript исключаются из использования this . Эта ситуация была похожа на следующую структуру JavaScript:

// Rhino runtime

// Apps Script built-in services defined here, in the actual global context.
var SpreadsheetApp = {
  openById: function() { ... }
  getActive: function() { ... }
  // etc.
};

function() {
  // Implicit special context; all your code goes here. If the global this
  // is referenced in your code, it only contains elements from this context.

  // Any global variables you defined.
  var x = 42;

  // Your script functions.
  function myFunction() {
    ...
  }
  // End of your code.
}();

В версии V8 неявный специальный контекст удален. Глобальные переменные и функции, определенные в скрипте, помещаются в глобальный контекст, рядом со встроенными службами Apps Script и встроенными функциями ECMAScript, такими как Math и Date .

При миграции вашего скрипта на V8 протестируйте и скорректируйте ожидания вашего кода относительно использования this в глобальном контексте. В большинстве случаев различия становятся очевидными только в том случае, если ваш код анализирует ключи или имена свойств глобального объекта this :

// Rhino runtime
var myGlobal = 5;

function myFunction() {

  // Only logs [myFunction, myGlobal];
  console.log(Object.keys(this));

  // Only logs [myFunction, myGlobal];
  console.log(
    Object.getOwnPropertyNames(this));
}





      
// V8 runtime
var myGlobal = 5;

function myFunction() {

  // Logs an array that includes the names
  // of Apps Script services
  // (CalendarApp, GmailApp, etc.) in
  // addition to myFunction and myGlobal.
  console.log(Object.keys(this));

  // Logs an array that includes the same
  // values as above, and also includes
  // ECMAScript built-ins like Math, Date,
  // and Object.
  console.log(
    Object.getOwnPropertyNames(this));
}

Скорректирована обработка оператора instanceof в библиотеках.

Использование instanceof в библиотеке для объекта, передаваемого в качестве параметра в функцию из другого проекта, может привести к ложным отрицательным результатам. В среде выполнения V8 проект и его библиотеки запускаются в разных контекстах выполнения и, следовательно, имеют разные глобальные переменные и цепочки прототипов.

Это справедливо только в том случае, если ваша библиотека использует instanceof для объекта, который не создан в вашем проекте. Использование его для объекта, созданного в вашем проекте, независимо от того, находится ли он в том же или другом скрипте внутри вашего проекта, должно работать как положено.

Если проект, работающий на V8, использует ваш скрипт в качестве библиотеки, проверьте, использует ли ваш скрипт instanceof для параметра, передаваемого из другого проекта. Скорректируйте использование instanceof и используйте другие подходящие альтернативы в зависимости от вашей задачи.

В качестве альтернативы для использования a instanceof b можно использовать конструктор ` a в тех случаях, когда нет необходимости искать по всей цепочке прототипов, а достаточно проверить только конструктор. Использование: a.constructor.name == "b"

Рассмотрим проект А и проект В, где проект А использует проект В в качестве библиотеки.

//Rhino runtime

//Project A

function caller() {
   var date = new Date();
   // Returns true
   return B.callee(date);
}

//Project B

function callee(date) {
   // Returns true
   return(date instanceof Date);
}

      
//V8 runtime

//Project A

function caller() {
   var date = new Date();
   // Returns false
   return B.callee(date);
}

//Project B

function callee(date) {
   // Incorrectly returns false
   return(date instanceof Date);
   // Consider using return (date.constructor.name ==
   // Date) instead.
   // return (date.constructor.name == Date) -> Returns
   // true
}

Другой альтернативой может быть введение функции, проверяющей instanceof в основной проект и передача этой функции в дополнение к другим параметрам при вызове функции из библиотеки. Переданная функция затем может быть использована для проверки instanceof внутри библиотеки.

//V8 runtime

//Project A

function caller() {
   var date = new Date();
   // Returns True
   return B.callee(date, date => date instanceof Date);
}

//Project B

function callee(date, checkInstanceOf) {
  // Returns True
  return checkInstanceOf(date);
}
      

Скорректировать передачу несовместно используемых ресурсов библиотекам.

Передача неразделяемого ресурса из основного скрипта в библиотеку в среде выполнения V8 работает иначе.

В среде выполнения Rhino передача неразделяемого ресурса не будет работать. Вместо этого библиотека использует собственный ресурс.

В среде выполнения V8 передача неразделяемого ресурса библиотеке работает. Библиотека использует переданный неразделяемый ресурс.

Не передавайте неразделяемые ресурсы в качестве параметров функций. Всегда объявляйте неразделяемые ресурсы в том же скрипте, который их использует.

Рассмотрим проект A и проект B, где проект A использует проект B в качестве библиотеки. В этом примере PropertiesService является неразделяемым ресурсом.

// Rhino runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-B
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

//Project B function setScriptProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

// V8 runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-A
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

// Project B function setProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

Рекомендации JDBC в среде выполнения V8

В среде выполнения V8 мы добавили новые функции в службу JDBC.

Используйте executeBatch для пакетных операций.

Используйте операцию executeBatch(params) для выполнения пакетных операций с базой данных.

В следующем примере показано, как вставить несколько строк в базу данных с помощью пакетной обработки:

Вот пример среды выполнения Rhino (старый метод):

var conn = Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");
var stmt = conn.prepareStatement("INSERT INTO employees (name, age) VALUES (?, ?)");
var params = [["John Doe", 30], ["John Smith", 25]];
for (var i = 0; i < params.length; i++) {
  stmt.setString(1, params[i][0]);
  stmt.setInt(2, params[i][1]);
  stmt.execute();
}

Вот среда выполнения V8 (новый метод):

var conn = Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");
var stmt = conn.prepareStatement("INSERT INTO employees (name, age) VALUES (?, ?)");
var params = [["John Doe", 30], ["John Smith", 25]];
stmt.executeBatch(params);

Используйте getRows для получения набора результатов.

Используйте getRows(queryString) для получения данных результирующего набора за один вызов. queryString состоит из разделенных запятыми вызовов методов-геттеров JdbcResultSet , например: "getString(1), getDouble('price'), getDate(3, 'UTC')" . Поддерживаются все методы-геттеры, отвечающие за чтение данных столбцов; например, getHoldability , getMetaData и т. д. не поддерживаются. Аргументами могут быть целочисленные индексы столбцов (начиная с 1) или строковые метки столбцов в одинарных или двойных кавычках.

В следующем примере показано, как извлечь строки из результирующего набора:

Вот пример среды выполнения Rhino (старый метод):

var conn = Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");
var stmt = conn.createStatement();
var rs = stmt.executeQuery("SELECT name, age FROM employees");
while (rs.next()) {
  Logger.log(rs.getString('name') + ", " + rs.getInt('age'));
}

Вот среда выполнения V8 (новый метод):

var conn = Jdbc.getCloudSqlConnection("jdbc:google:mysql://...");
var stmt = conn.createStatement();
var rs = stmt.executeQuery("SELECT name, age FROM employees");
var rows = rs.getRows("getString('name'), getInt('age')");
for (var i = 0; i < rows.length; i++) {
  Logger.log(rows[i][0] + ", " + rows[i][1]);
}

Обновить доступ к автономным скриптам

Для автономных скриптов, работающих в среде выполнения V8, необходимо предоставить пользователям как минимум доступ на просмотр скрипта, чтобы триггеры скрипта работали корректно.