Migrar scripts para o tempo de execução V8

O ambiente de execução do Rhino será descontinuado em 31 de janeiro de 2026 ou após essa data. Se você tiver um script que usa o ambiente de execução do Rhino, migre-o para o V8.

Muitas vezes, o único pré-requisito para adicionar a sintaxe e os recursos do V8 a um script é ativar o ambiente de execução do V8. No entanto, há um pequeno conjunto de incompatibilidades e outras diferenças que podem resultar em falha ou comportamento inesperado de um script no ambiente de execução do V8. Ao migrar um script para usar o V8, pesquise o projeto de script para encontrar esses problemas e corrija os que encontrar.

Procedimento de migração do V8

Para migrar um script para o V8, siga este procedimento:

  1. Ative o ambiente de execução do V8 para o script. O runtimeVersion pode ser verificado usando o manifesto do projeto do Google Apps Script.
  2. Analise com atenção as incompatibilidades a seguir. Examine seu script para determinar se alguma das incompatibilidades está presente. Se uma ou mais incompatibilidades estiverem presentes, ajuste o código do script para remover ou evitar o problema.
  3. Analise com atenção as outras diferenças a seguir. Examine seu script para determinar se alguma das diferenças listadas afeta o comportamento do código. Ajuste o script para corrigir o comportamento.
  4. Depois de corrigir as incompatibilidades ou outras diferenças descobertas, comece a atualizar o código para usar a sintaxe e outros recursos do V8.
  5. Depois de terminar os ajustes de código, teste o script para garantir que ele se comporte conforme o esperado.
  6. Se o script for um app da Web ou um complemento publicado, você deve criar uma nova versão do script com os ajustes do V8 e direcionar a implantação para a versão recém-criada. Para disponibilizar a versão do V8 aos usuários, republique o script com essa versão.
  7. Se o script for usado como uma biblioteca, crie uma nova implantação com versão do script. Comunique essa nova versão a todos os scripts e usuários que consomem sua biblioteca, instruindo-os a atualizar para a versão ativada pelo V8. Verifique se as versões mais antigas da biblioteca baseadas no Rhino não estão mais em uso ou acessíveis.
  8. Verifique se nenhuma instância do script ainda está operando no ambiente de execução legado do Rhino. Verifique se todas as implantações estão associadas a uma versão no V8. Arquive implantações antigas. Analise todas as versões e exclua as versões que não estão usando o ambiente de execução do V8.

Incompatibilidades

O ambiente de execução original do Apps Script baseado no Rhino permitia vários comportamentos não padrão do ECMAScript. Como o V8 está em conformidade com os padrões, esses comportamentos não são aceitos após a migração. Se você não corrigir esses problemas, ocorrerão erros ou o comportamento do script será interrompido quando o ambiente de execução do V8 estiver ativado.

As seções a seguir descrevem cada um desses comportamentos e as etapas que você precisa seguir para corrigir o código do script durante a migração para o V8.

Evite for each(variable in object)

A instrução for each (variable in object) foi adicionada ao JavaScript 1.6 e removida em favor de for...of.

Ao migrar o script para o V8, evite usar for each (variable in object) instruções.

Em vez disso, use 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);
}
      

Evite Date.prototype.getYear()

No ambiente de execução original do Rhino, Date.prototype.getYear() retorna anos de dois dígitos para anos de 1900 a 1999, mas anos de quatro dígitos para outras datas, que era o comportamento no JavaScript 1.2 e versões anteriores.

No ambiente de execução do V8, Date.prototype.getYear() retorna o ano menos 1900, conforme exigido pelos padrões ECMAScript.

Ao migrar o script para o V8, sempre use Date.prototype.getFullYear(), que retorna um ano de quatro dígitos, independentemente da data.

Evite usar palavras-chave reservadas como nomes

O ECMAScript proíbe o uso de determinadas palavras-chave reservadas em nomes de funções e variáveis. O ambiente de execução do Rhino permitia muitas dessas palavras. Portanto, se o código as usar, renomeie as funções ou variáveis.

Ao migrar o script para o V8, evite nomear variáveis ou funções usando uma das palavras-chave reservadas. Renomeie qualquer variável ou função para evitar o uso do nome da palavra-chave. Os usos comuns de palavras-chave como nomes são class, import e export.

Uma exceção é que os literais de objeto podem usar palavras-chave reservadas (em todos os ambientes de execução):

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

Evite reatribuir variáveis const

No ambiente de execução original do Rhino, é possível declarar uma variável usando const, o que significa que o valor do símbolo nunca muda e as atribuições futuras ao símbolo são ignoradas.

No novo ambiente de execução do V8, a const está em conformidade com os padrões, e a atribuição a uma variável declarada como const resulta em um TypeError: Assignment to constant variable erro de ambiente de execução.

Ao migrar o script para o V8, não tente reatribuir o valor de uma const variável:

// 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
      

Evite literais XML e o objeto XML

Essa extensão não padrão do ECMAScript permite que os projetos do Apps Script usem a sintaxe XML diretamente.

Ao migrar o script para o V8, evite usar literais XML diretos ou o objeto XML.

Em vez disso, use o XmlService para analisar 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
      

Não crie funções de iterador personalizadas usando __iterator__

O JavaScript 1.7 adicionou um recurso para permitir a adição de um iterador personalizado a qualquer classe declarando uma função __iterator__ no protótipo dessa classe. Isso também foi adicionado ao ambiente de execução do Rhino do Apps Script como uma conveniência para o desenvolvedor. No entanto, esse recurso nunca fez parte do padrão ECMA-262 e foi removido em mecanismos JavaScript compatíveis com ECMAScript. Os scripts que usam o V8 não podem usar essa construção de iterador.

Ao migrar o script para o V8, evite a função __iterator__ para criar iteradores personalizados. Em vez disso, use iteradores ECMAScript 6.

Considere a seguinte construção de matriz:

// 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.
      

Os exemplos de código a seguir mostram como um iterador pode ser construído no ambiente de execução do Rhino e como construir um iterador de substituição no ambiente de execução do 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);
}
      

No ambiente de execução do V8, é necessário usar for...of ao percorrer matrizes com iteradores personalizados, já que for..in não espera iteráveis.

Evite cláusulas de captura condicional

O ambiente de execução do V8 não oferece suporte a cláusulas de captura condicional catch..if, porque elas não estão em conformidade com os padrões.

Ao migrar o script para o V8, mova todas as condicionais de captura para dentro do corpo de captura:

// 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
  }
}

Evite usar Object.prototype.toSource()

O JavaScript 1.3 continha um Object.prototype.toSource() que nunca fez parte de nenhum padrão ECMAScript. Ele não é aceito no ambiente de execução do V8.

Ao migrar o script para o V8, remova qualquer uso de Object.prototype.toSource() do código.

Outras diferenças

Além das incompatibilidades anteriores que podem causar falhas de script, há algumas outras diferenças que, se não forem corrigidas, poderão resultar em um comportamento inesperado do script do ambiente de execução do V8.

As seções a seguir explicam como atualizar o código do script para evitar essas surpresas inesperadas.

Ajustar a formatação de data e hora específica da localidade

Os Date métodos toLocaleString(), toLocaleDateString(), e toLocaleTimeString() se comportam de maneira diferente no ambiente de execução do V8 em comparação com o Rhino.

No Rhino, o formato padrão é o formato longo, e todos os parâmetros transmitidos são ignorados.

No ambiente de execução do V8, o formato padrão é o formato curto e os parâmetros transmitidos são processados de acordo com o padrão ECMA (consulte a toLocaleDateString() documentação para mais detalhes).

Ao migrar o script para o V8, teste e ajuste as expectativas do código em relação à saída de métodos de data e hora específicos da localidade:

// 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' }));
      

Evite usar Error.fileName e Error.lineNumber

No ambiente de execução do V8, o objeto JavaScript padrão Error não oferece suporte a fileName ou lineNumber como parâmetros de construtor ou propriedades de objeto.

Ao migrar o script para o V8, remova qualquer dependência de Error.fileName e Error.lineNumber.

Uma alternativa é usar o Error.prototype.stack. Essa pilha também não é padrão, mas é aceita no V8. O formato do rastreamento de pilha produzido pelas duas plataformas é ligeiramente diferente:

// 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)
      

Ajustar o processamento de objetos de enumeração stringificados

No ambiente de execução original do Rhino, o uso do método JavaScript JSON.stringify() em um objeto de enumeração retorna apenas {}.

No V8, o uso do mesmo método em um objeto de enumeração retorna o nome da enumeração.

Ao migrar o script para o V8, teste e ajuste as expectativas do código em relação à saída de JSON.stringify() em objetos de enumeração:

// 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"

Ajustar o processamento de parâmetros indefinidos

No ambiente de execução original do Rhino, a transmissão de undefined para um método como parâmetro resultava na transmissão da string "undefined" para esse método.

No V8, a transmissão de undefined para métodos é equivalente à transmissão de null.

Ao migrar o script para o V8, teste e ajuste as expectativas do código em relação aos parâmetros 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.

Ajustar o processamento de this global

O ambiente de execução do Rhino define um contexto especial implícito para scripts que o usam. O código do script é executado nesse contexto implícito, distinto do this global real. Isso significa que as referências ao "global this" no código são avaliadas como o contexto especial, que contém apenas o código e as variáveis definidas no script. Os serviços integrados do Apps Script e os objetos ECMAScript são excluídos desse uso de this. Essa situação era semelhante a esta estrutura 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.
}();

No V8, o contexto especial implícito é removido. As variáveis e funções globais definidas no script são colocadas no contexto global, ao lado dos serviços integrados do Apps Script e dos recursos integrados do ECMAScript, como Math e Date.

Ao migrar o script para o V8, teste e ajuste as expectativas do código em relação ao uso de this em um contexto global. Na maioria dos casos, as diferenças só são aparentes se o código examinar as chaves ou nomes de propriedades do objeto this global:

// 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));
}

Ajustar o processamento de instanceof em bibliotecas

O uso de instanceof em uma biblioteca em um objeto transmitido como parâmetro em uma função de outro projeto pode gerar falsos negativos. No ambiente de execução do V8, um projeto e as bibliotecas são executados em contextos de execução diferentes e, portanto, têm globais e cadeias de protótipo diferentes.

Isso só acontece se a biblioteca usar instanceof em um objeto que não foi criado no projeto. O uso em um objeto criado no projeto, seja no mesmo script ou em um script diferente dentro do projeto, deve funcionar conforme o esperado.

Se um projeto em execução no V8 usar seu script como uma biblioteca, verifique se o seu script usa instanceof em um parâmetro transmitido de outro projeto. Ajuste o uso de instanceof e use outras alternativas viáveis de acordo com seu caso de uso.

Uma alternativa para a instanceof b pode ser usar o construtor de a em casos em que não é necessário pesquisar toda a cadeia de protótipos e apenas verificar o construtor. Uso: a.constructor.name == "b"

Considere o projeto A e o projeto B, em que o projeto A usa o projeto B como uma biblioteca.

//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
}

Outra alternativa é introduzir uma função que verifica instanceof no projeto principal e transmitir a função além de outros parâmetros ao chamar uma função de biblioteca. A função transmitida pode ser usada para verificar instanceof dentro da biblioteca.

//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);
}
      

Ajustar a transmissão de recursos não compartilhados para bibliotecas

A transmissão de um recurso não compartilhado do script principal para uma biblioteca funciona de maneira diferente no ambiente de execução do V8.

No ambiente de execução do Rhino, a transmissão de um recurso não compartilhado não funciona. A biblioteca usa o próprio recurso.

No ambiente de execução do V8, a transmissão de um recurso não compartilhado para a biblioteca funciona. A biblioteca usa o recurso não compartilhado transmitido.

Não transmita recursos não compartilhados como parâmetros de função. Sempre declare recursos não compartilhados no mesmo script que os usa.

Considere o projeto A e o projeto B, em que o projeto A usa o projeto B como uma biblioteca. Neste exemplo, PropertiesService é um recurso não compartilhado.

// 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); }

Recomendações de JDBC no ambiente de execução do V8

Com o ambiente de execução do V8, adicionamos novos recursos ao serviço JDBC.

Usar executeBatch para operações em lote

Use operações executeBatch(params) para realizar operações de banco de dados em lote.

O exemplo a seguir mostra como inserir várias linhas em um banco de dados usando o lote:

Confira o ambiente de execução do Rhino (método antigo):

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();
}

Confira o ambiente de execução do V8 (novo método):

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);

Usar getRows para buscar o conjunto de resultados

Use getRows(queryString) para buscar dados do conjunto de resultados em uma única chamada. O queryString consiste em chamadas separadas por vírgulas para métodos getter de JdbcResultSet, por exemplo: "getString(1), getDouble('price'), getDate(3, 'UTC')". Os métodos aceitos incluem todos os métodos getter responsáveis pela leitura de dados de coluna. Por exemplo, getHoldability, getMetaData etc. não são aceitos. Os argumentos podem ser índices de coluna inteiros (baseados em 1) ou rótulos de coluna de string entre aspas simples ou duplas.

O exemplo a seguir mostra como buscar linhas do conjunto de resultados:

Confira o ambiente de execução do Rhino (método antigo):

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'));
}

Confira o ambiente de execução do V8 (novo método):

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]);
}

Atualizar o acesso a scripts independentes

Para scripts independentes em execução no ambiente de execução do V8, é necessário fornecer aos usuários pelo menos acesso de visualização ao script para que os acionadores do script funcionem corretamente.