Skip to content

Instantly share code, notes, and snippets.

@igorlima
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save igorlima/61da11107ef1cb5a08bb to your computer and use it in GitHub Desktop.
Save igorlima/61da11107ef1cb5a08bb to your computer and use it in GitHub Desktop.

Nota do editor: Bem-vindo à terceira parte de uma série de três, que visa ajudá-lo a ter um empurrãozinho nas novidades e quebras de APIs do Node v0.12. Na primeira parte, Alex Gorbatchev extraiu as APIs que não vão quebrar. Já na segunda parte, ele separou as que irão. E na terceira, Ben Noordhuis mostra detalhes nas mudanças da API C++.

Houve inúmeras mudanças entre as versões v0.10 e v0.12 do Node. A ideia dessa série não é tentar destacar cada uma das últimas mudanças, mas sim agrupar as mudanças relacionadas, classificando-as numa ordem (indiscutivelmente de caráter bastante subjetivo) de "mais importante" para "menos importante" e explicar como atualizar melhor o código.

Boa parte deste artigo abrange a API V8 pelo fato de ser a API que mais teve mudanças significativas. Comentários e sugestões são muito bem-vindos! Sinta-se a vontade para entrar em contato com o autor pelo perfil do ben no strongloop.com.

O prototype das funções nativas teve alterações

Na versão v0.10, se escreve:

v8::Handle<v8::Value> FortyTwo(const v8::Arguments& args) {
  v8::HandleScope handle_scope;
  return handle_scope.Close(v8::Integer::New(42));
}

Na versão v0.12, escreve:

void FortyTwo(const v8::FunctionCallbackInfo<v8::Value>& info) {
  // Don't need a HandleScope in this particular example.
  info.GetReturnValue().Set(42);
}

As importantes alterações são:

  1. O tipo de retorno é void.
  2. O valor de retorno é alterado usando v8::ReturnValue::Set(), v8::ReturnValue::SetEmptyString(), v8::ReturnValue::SetNull() ou v8::ReturnValue::SetUndefined().
  3. undefined é retornado se o valor de retorno não for alterado de forma explicita.

V8::ReturnValue::Set() tem um número de sobrecargas de método que permite suportar argumento de diferentes tipos. Se o compilador tiver dificuldade em resolver algum tipo ou reclamar de ambigüidade, o argumento static_cast<> serve para a conversão:

void FortyTwo(const v8::FunctionCallbackInfo<v8::Value>& info) {

  const int64_t value = 42;

  // error: call of overloaded 'Set(const int64_t&)' is ambiguous

  info.GetReturnValue().Set(value);

  // but this works

  info.GetReturnValue().Set(static_cast<int32_t>(value));

  // as does this

  info.GetReturnValue().Set(static_cast<double>(value));

}

Lógico que é preciso escolher o tipo certo para a conversão. Visto que uma conversão explicita para um outro tipo que não seja amplo o suficiente para manter o valor original, pode gerar resultados inesperados.

Observe que v8::FunctionCallbackInfo <v8::Value> é um v8::Arguments renomeado e parametrizado. Além do nome e a adição do método GetReturnValue(), não houve nenhuma outra mudança.

A maioria das funções da API V8 agora usa um v8::Isolate*

Na versão v0.10, se escreve:

v8::Local<v8::Integer> a = v8::Integer::New(42);

v8::Local<v8::Number> b = v8::Number::New(13.37);

v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(Foo);

Na versão v0.12, escreve:

v8::Isolate* isolate = /* ... */;

v8::Local<v8::Integer> a = v8::Integer::New(isolate, 42);

v8::Local<v8::Number> b = v8::Number::New(isolate, 13.37);

v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(isolate, Foo);

É possível obter um ponteiro para o v8::Isolate corrente de várias maneiras:

void FortyTwo(const v8::FunctionCallbackInfo<v8::Value>& info) {

  v8::Isolate* isolate;

  isolate = info.GetIsolate();

  // or:

  isolate = info.GetReturnValue().GetIsolate();

  // or even:

  isolate = v8::Isolate::GetCurrent();

}

Note que a equipe V8 advertiu que v8::Isolate::GetCurrent() vai ser extinto. Caso queira um código que dure até o futuro próximo, é melhor passar explicitamente o isolate.

Funções de string V8 agora deve explicitar sua codificação

Na versão v0.10, se escreve:

// create a UTF-8 string
v8::Local<v8::String> utf8 = v8::String::New("42");
// create a UTF-16 string from input in system endianness
static const uint16_t chars[] = { '4', '2', 0 };
v8::Local<v8::String> utf16 = v8::String::New(chars);

Na versão v0.12, escreve:

v8::Isolate* isolate = /* ... */;
// create a UTF-8 string
v8::Local<v8::String> utf8 = v8::String::NewFromUtf8(isolate, "42");
// create a UTF-16 string from input in system endianness
static const uint16_t chars[] = { '4', '2', 0 };
v8::Local<v8::String> utf16 = v8::String::NewFromTwoByte(isolate, chars);
// create a ISO-8859-1 a.k.a. Latin1 string
const uint8_t* octets = reinterpret_cast<const uint8_t*>("42");
v8::Local<v8::String> latin1 = v8::String::NewFromOneByte(isolate, octets);

v8::String::NewFromTwoByte() é um nome tanto inadequado uma vez que não é uma codificação estrita de dois bytes e sim, algo que reconhece surrogate pair, portanto um método que consome caracteres UTF-16 e não UCS-2.

Nota: um Surrogate é um tipo de caractere, utilizado em algumas linguagens, que altera ou substitui um grupo de dois caracteres. Um Surrogate Pair com certeza tem mais de 2 bytes.

Remoção de v8::String::NewSymbol() e v8::String::NewUndetectable()

Na versão v0.10, escreve:

v8::Local<v8::String> symbol = v8::String::NewSymbol("42");
v8::Local<v8::String> hidden = v8::String::NewUndetectable("42");

Na versão v0.12, escreve:

v8::Isolate* isolate = /* ... */;
v8::Local<v8::String> symbol =
    v8::String::NewFromUtf8(isolate, "42", v8::String::kInternalizedString);
v8::Local<v8::String> hidden =
    v8::String::NewFromUtf8(isolate, "42", v8::String::kUndetectableString);

Remoção de v8::String::AsciiValue

Na versão v0.10, escreve:

v8::Local<v8::String> string = /* ... */;
v8::String::AsciiValue s(string);
puts(s);

Na versão v0.12, escreve:

Nada! V8::String::AsciiValue está quebrado: seu nome sugere um retorno de um dado ASCII de 7 bits, mas o que ele realmente retorna é um dado binário de 8 bits. E também quebra as seqüências de caracteres de múltiplos bytes.

Substitua-o com v8::String::Utf8Value ou v8::String::Value (para UTF-16). Caso queira extrair bytes brutos de uma string, faça isso:

v8::Local<v8::String> string = /* ... */;
const int length = string->Utf8Length() + 1;  // Add one for trailing zero byte.
uint8_t* buffer = new uint8_t[length];
string->WriteOneByte(buffer, /* start */ 0, length);

Cuidado: v8::String::Utf8Length() pode iterar sobre cada caractere, e por isso pode ser (muito) lenta para string bem maiores.

Remoção de v8::HandleScope::Close()

Na versão v0.10, escreve:

v8::Local<v8::Value> Example() {
  v8::HandleScope handle_scope;
  return handle_scope.Close(v8::Integer::New(42));
}

Na versão v0.12, escreve:

v8::Local<v8::Value> Example(v8::Isolate* isolate) {
  v8::EscapableHandleScope handle_scope(isolate);
  return handle_scope.Escape(v8::Integer::New(isolate, 42));
}

v8::Persistent não herda mais de v8::Handle

v8::Persistent<T> não é mais uma instância de v8::Handle<T>. Isso significa que não há acesso direto ao Handle. A motivação para essa mudança é que o comportamento da versão 0.10 facilmente apresenta (i) perda de recursos ou (ii) há a possibilidade de usar um valor mesmo depois de ter sido descartado.

Na versão v0.10, escreve:

v8::Local<v8::Value> value = /* ... */;
v8::Persistent<v8::Value> persistent = v8::Persistent<v8::Value>::New(value);
// ...
v8::Local<v8::Value> value_again = *persistent;
// ...
persistent.Dispose();
persistent.Clear();

Na versão v0.12, escreve:

v8::Isolate* isolate = /* ... */
v8::Local<v8::Value> value = /* ... */;
v8::Persistent<v8::Value> persistent(isolate, value);
// or:
v8::Persistent<v8::Value> persistent;
persistent.Reset(isolate, value);
// ...
v8::Local<v8::Value> value_again =
    v8::Local<v8::Value>::New(isolate, persistent);  // rematerialize handle
// ...
persistent.Reset();

Outra mudança é que os Handles persistentes, por padrão, não são mais copiáveis. Na versão v0.10, escreve:

v8::Local<v8::Value> value = /* ... */;
v8::Persistent<v8::Value> a = v8::Persistent<v8::Value>::New(value);
v8::Persistent<v8::Value> b = a;  // a and b now point to |value|

Na versão v0.12, escreve:

v8::Isolate* isolate = /* ... */
v8::Local<v8::Value> value = /* ... */;
v8::Persistent<v8::Value> a(isolate, value);
v8::Persistent<v8::Value> b(isolate, a);
// or:
v8::Persistent<v8::Value> b;
b.Reset(isolate, a);

Uma consequência de v8::Persistent<T> não ter a semântica de cópia é que isso torna mais difícil o uso de classes container STL. Por esse motivo, V8 fornece um número de classes utilitárias no arquivo de cabeçalho v8-util.h.

Exemplo de uso:

#include "v8-util.h"
 
void Example(v8:Isolate* isolate) {
  v8::StdPersistentValueMap<int, v8::String> map(isolate);
  v8::Local<v8::String> value = v8::String::NewFromUtf8(isolate, "fortytwo");
  map.Set(42, value);
  assert(map.Contains(42));
  assert(map.Get(42)->StrictEquals(value));
  assert(map.Get(21).IsEmpty());
  assert(1 == map.Size());
  map.Remove(42);
  assert(0 == map.Size());
  map.Clear();  // or we could just call .Clear()
}

Usando o C++ 11, é possível usar o v8::UniquePersistent<T> em classes container padrão com semântica alterável.

Uma alternativa, é criar um v8::Persistent com traços copiáveis, mas faça isso somente quando tiver a certeza de que não há espaço para a perda de recursos ou o uso é livre de bugs:

v8::Isolate* isolate = /* ... */;
v8::CopyablePersistentTraits<v8::Value>::CopyablePersistent persistent;
persistent.Reset(isolate, /* ... */);

O prototype do callback para os handles fracamente persistente foi alterado

Um handle persistente normal é ignorado pelo garbage collector e permanece em memória até que seja descartado manualmente.

Já um handle fracamente persistente , por outro lado, é controlado pelo garbage collector e, quando está prestes a ser recolhido, o coletor notifica o programa através de um callback.

O programa então libera quaisquer recursos associado ao handle ou o revive caso o objeto deve permanecer em memória por mais tempo.

Na versão v0.10, escreve:

void WeakCallback(v8::Persistent<v8::Value> object, void* arg) {
  puts(static_cast<const char*>(arg));
  object.Dispose();  // or .ClearWeak() if you want to keep it around
}
 
void Example() {
  v8::HandleScope handle_scope;
  v8::Local<v8::Object> object = v8::Object::New();
  v8::Persistent<v8::Object> persistent =
      v8::Persistent<v8::Object>::New(object);
  persistent.MakeWeak(const_cast<char*>("fortytwo"), WeakCallback);
}

Na versão v0.12, escreve:

void WeakCallback(const v8::WeakCallbackData<v8::Value, const char*>& data) {
  puts(data.GetParameter());
  // The persistent's storage cell is automatically disposed.  Keep a reference
  // to the original v8::Persistent<T> if you want to revive it with
  // .ClearWeak().
}
 
void Example(v8::Isolate* isolate) {
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Object> object = v8::Object::New(isolate);
  v8::Persistent<v8::Object> persistent(isolate, object);
  persistent.SetWeak("fortytwo", WeakCallback);
}

v8::ThrowException() agora é v8::Isolate::ThrowException()

Na versão v0.10, escreve:

v8::Local<v8::Value> exception = /* ... */;
v8::ThrowException(exception);

Na versão v0.12, escreve:

v8::Isolate* isolate = /* ... */
v8::Local<v8::Value> exception = /* ... */;
isolate->ThrowException(exception);

E o que mais?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment