Skip to content

Domínio

[Histórico]

VersãoMudanças
v8.8.0Quaisquer Promises criadas em contextos VM não têm mais uma propriedade .domain. Seus manipuladores ainda são executados no domínio adequado, no entanto, e as Promises criadas no contexto principal ainda possuem uma propriedade .domain.
v8.0.0Os manipuladores para Promises agora são invocados no domínio em que a primeira promessa de uma cadeia foi criada.
v1.4.2Obsoleto desde: v1.4.2

[Estável: 0 - Obsoleto]

Estável: 0 Estabilidade: 0 - Obsoleto

Código-fonte: lib/domain.js

Este módulo está pendente de descontinuação. Assim que uma API de substituição for finalizada, este módulo será totalmente descontinuado. A maioria dos desenvolvedores não deve ter motivos para usar este módulo. Usuários que absolutamente precisam da funcionalidade que os domínios fornecem podem confiar nela por enquanto, mas devem esperar ter que migrar para uma solução diferente no futuro.

Os domínios fornecem uma maneira de lidar com várias operações de E/S diferentes como um único grupo. Se algum dos emissores de eventos ou callbacks registrados em um domínio emitir um evento 'error', ou lançar um erro, o objeto domínio será notificado, em vez de perder o contexto do erro no manipulador process.on('uncaughtException'), ou fazer com que o programa seja encerrado imediatamente com um código de erro.

Aviso: Não ignore erros!

Os manipuladores de erros de domínio não substituem o encerramento de um processo quando ocorre um erro.

Pela própria natureza de como throw funciona em JavaScript, quase nunca há uma maneira segura de "retomar de onde parou", sem vazar referências ou criar algum outro tipo de estado frágil indefinido.

A maneira mais segura de responder a um erro lançado é encerrar o processo. Obviamente, em um servidor web normal, pode haver muitas conexões abertas, e não é razoável encerrá-las abruptamente porque um erro foi acionado por outra pessoa.

A melhor abordagem é enviar uma resposta de erro para a requisição que acionou o erro, enquanto permite que as outras terminem em seu tempo normal, e parar de ouvir novas requisições nesse trabalhador.

Dessa forma, o uso de domain anda de mãos dadas com o módulo cluster, já que o processo principal pode criar um novo trabalhador quando um trabalhador encontra um erro. Para programas Node.js que escalam para várias máquinas, o proxy de encerramento ou o registro de serviço podem tomar nota da falha e reagir de acordo.

Por exemplo, esta não é uma boa ideia:

js
// XXX AVISO! MÁ IDEIA!

const d = require('node:domain').create()
d.on('error', er => {
  // O erro não vai travar o processo, mas o que ele faz é pior!
  // Embora tenhamos impedido a reinicialização abrupta do processo, estamos vazando
  // muitos recursos se isso acontecer.
  // Isso não é melhor do que process.on('uncaughtException')!
  console.log(`erro, mas tudo bem ${er.message}`)
})
d.run(() => {
  require('node:http')
    .createServer((req, res) => {
      handleRequest(req, res)
    })
    .listen(PORT)
})

Ao usar o contexto de um domínio e a resiliência de separar nosso programa em vários processos de trabalhadores, podemos reagir de forma mais apropriada e lidar com erros com muito mais segurança.

js
// Muito melhor!

const cluster = require('node:cluster')
const PORT = +process.env.PORT || 1337

if (cluster.isPrimary) {
  // Um cenário mais realista teria mais de 2 trabalhadores,
  // e talvez não colocaria o primário e o trabalhador no mesmo arquivo.
  //
  // Também é possível ser um pouco mais sofisticado sobre o registro, e
  // implementar qualquer lógica personalizada que seja necessária para impedir DoS
  // ataques e outros comportamentos ruins.
  //
  // Veja as opções na documentação do cluster.
  //
  // O importante é que o primário faça muito pouco,
  // aumentando nossa resiliência a erros inesperados.

  cluster.fork()
  cluster.fork()

  cluster.on('disconnect', worker => {
    console.error('desconectar!')
    cluster.fork()
  })
} else {
  // o trabalhador
  //
  // É aqui que colocamos nossos bugs!

  const domain = require('node:domain')

  // Consulte a documentação do cluster para mais detalhes sobre o uso
  // de processos de trabalhadores para atender requisições. Como funciona, ressalvas, etc.

  const server = require('node:http').createServer((req, res) => {
    const d = domain.create()
    d.on('error', er => {
      console.error(`erro ${er.stack}`)

      // Estamos em território perigoso!
      // Por definição, algo inesperado ocorreu,
      // o que provavelmente não queríamos.
      // Qualquer coisa pode acontecer agora! Tenha muito cuidado!

      try {
        // Certifique-se de que fechamos em 30 segundos
        const killtimer = setTimeout(() => {
          process.exit(1)
        }, 30000)
        // Mas não mantenha o processo aberto apenas para isso!
        killtimer.unref()

        // Pare de receber novas requisições.
        server.close()

        // Avise ao primário que estamos mortos. Isso acionará um
        // 'disconnect' no cluster primário, e então ele irá gerar
        // um novo trabalhador.
        cluster.worker.disconnect()

        // Tente enviar um erro para a requisição que acionou o problema
        res.statusCode = 500
        res.setHeader('content-type', 'text/plain')
        res.end('Oops, houve um problema!\n')
      } catch (er2) {
        // Ah, bem, não há muito que possamos fazer neste ponto.
        console.error(`Erro ao enviar 500! ${er2.stack}`)
      }
    })

    // Como req e res foram criados antes que este domínio existisse,
    // precisamos adicioná-los explicitamente.
    // Consulte a explicação de vinculação implícita versus explícita abaixo.
    d.add(req)
    d.add(res)

    // Agora execute a função manipuladora no domínio.
    d.run(() => {
      handleRequest(req, res)
    })
  })
  server.listen(PORT)
}

// Esta parte não é importante. Apenas um exemplo de roteamento.
// Coloque aqui uma lógica de aplicativo sofisticada.
function handleRequest(req, res) {
  switch (req.url) {
    case '/error':
      // Fazemos algumas coisas assíncronas e então...
      setTimeout(() => {
        // Opa!
        flerb.bark()
      }, timeout)
      break
    default:
      res.end('ok')
  }
}

Adições a objetos Error

Sempre que um objeto Error é roteado através de um domínio, alguns campos extras são adicionados a ele.

  • error.domain O domínio que lidou com o erro pela primeira vez.
  • error.domainEmitter O emissor de eventos que emitiu um evento 'error' com o objeto de erro.
  • error.domainBound A função de callback que foi vinculada ao domínio e recebeu um erro como seu primeiro argumento.
  • error.domainThrown Um booleano indicando se o erro foi lançado, emitido ou passado para uma função de callback vinculada.

Vinculação implícita

Se os domínios estiverem em uso, todos os novos objetos EventEmitter (incluindo objetos Stream, requisições, respostas, etc.) serão implicitamente vinculados ao domínio ativo no momento de sua criação.

Além disso, as callbacks passadas para requisições de loop de eventos de baixo nível (como para fs.open() ou outros métodos que recebem callback) serão automaticamente vinculadas ao domínio ativo. Se elas lançarem um erro, o domínio irá capturar o erro.

Para evitar o uso excessivo de memória, os próprios objetos Domain não são implicitamente adicionados como filhos do domínio ativo. Se fossem, seria muito fácil impedir que objetos de requisição e resposta fossem devidamente coletados pelo garbage collector.

Para aninhar objetos Domain como filhos de um Domain pai, eles devem ser adicionados explicitamente.

A vinculação implícita roteia erros lançados e eventos 'error' para o evento 'error' do Domain, mas não registra o EventEmitter no Domain. A vinculação implícita cuida apenas de erros lançados e eventos 'error'.

Vinculação explícita

Às vezes, o domínio em uso não é o que deveria ser usado para um emissor de eventos específico. Ou, o emissor de eventos pode ter sido criado no contexto de um domínio, mas deveria, em vez disso, ser vinculado a algum outro domínio.

Por exemplo, pode haver um domínio em uso para um servidor HTTP, mas talvez gostaríamos de ter um domínio separado para usar para cada requisição.

Isso é possível por meio da vinculação explícita.

js
// Crie um domínio de nível superior para o servidor
const domain = require('node:domain')
const http = require('node:http')
const serverDomain = domain.create()

serverDomain.run(() => {
  // O servidor é criado no escopo do serverDomain
  http
    .createServer((req, res) => {
      // Req e res também são criados no escopo do serverDomain
      // no entanto, preferimos ter um domínio separado para cada requisição.
      // crie-o primeiro e adicione req e res a ele.
      const reqd = domain.create()
      reqd.add(req)
      reqd.add(res)
      reqd.on('error', er => {
        console.error('Erro', er, req.url)
        try {
          res.writeHead(500)
          res.end('Ocorreu um erro, desculpe.')
        } catch (er2) {
          console.error('Erro ao enviar 500', er2, req.url)
        }
      })
    })
    .listen(1337)
})

domain.create()

Classe: Domain

A classe Domain encapsula a funcionalidade de rotear erros e exceções não capturadas para o objeto Domain ativo.

Para lidar com os erros que ele captura, ouça seu evento 'error'.

domain.members

Um array de timers e emissores de eventos que foram explicitamente adicionados ao domínio.

domain.add(emitter)

Adiciona explicitamente um emissor ao domínio. Se algum manipulador de eventos chamado pelo emissor lançar um erro, ou se o emissor emitir um evento 'error', ele será roteado para o evento 'error' do domínio, da mesma forma que com o vínculo implícito.

Isso também funciona com timers retornados de setInterval() e setTimeout(). Se sua função de callback lançar um erro, ele será capturado pelo manipulador 'error' do domínio.

Se o Timer ou EventEmitter já estava vinculado a um domínio, ele é removido daquele e vinculado a este.

domain.bind(callback)

A função retornada será um wrapper em torno da função de callback fornecida. Quando a função retornada for chamada, quaisquer erros que forem lançados serão roteados para o evento 'error' do domínio.

js
const d = domain.create()

function readSomeFile(filename, cb) {
  fs.readFile(
    filename,
    'utf8',
    d.bind((er, data) => {
      // Se isso lançar um erro, ele também será passado para o domínio.
      return cb(er, data ? JSON.parse(data) : null)
    })
  )
}

d.on('error', er => {
  // Ocorreu um erro em algum lugar. Se lançarmos isso agora, o programa irá falhar
  // com o número da linha normal e a mensagem da pilha.
})

domain.enter()

O método enter() é um mecanismo usado pelos métodos run(), bind() e intercept() para definir o domínio ativo. Ele define domain.active e process.domain para o domínio e, implicitamente, empurra o domínio para a pilha de domínios gerenciada pelo módulo de domínio (veja domain.exit() para detalhes sobre a pilha de domínios). A chamada para enter() delimita o início de uma cadeia de chamadas assíncronas e operações de E/S vinculadas a um domínio.

Chamar enter() altera apenas o domínio ativo e não altera o próprio domínio. enter() e exit() podem ser chamados um número arbitrário de vezes em um único domínio.

domain.exit()

O método exit() sai do domínio atual, retirando-o da pilha de domínios. Sempre que a execução for mudar para o contexto de uma cadeia diferente de chamadas assíncronas, é importante garantir que o domínio atual seja encerrado. A chamada para exit() delimita o fim ou uma interrupção na cadeia de chamadas assíncronas e operações de E/S vinculadas a um domínio.

Se houver vários domínios aninhados vinculados ao contexto de execução atual, exit() encerrará todos os domínios aninhados dentro deste domínio.

Chamar exit() altera apenas o domínio ativo e não altera o próprio domínio. enter() e exit() podem ser chamados um número arbitrário de vezes em um único domínio.

domain.intercept(callback)

Este método é quase idêntico a domain.bind(callback). No entanto, além de capturar erros lançados, ele também interceptará objetos Error enviados como o primeiro argumento para a função.

Desta forma, o padrão comum if (err) return callback(err); pode ser substituído por um único manipulador de erros em um único lugar.

js
const d = domain.create()

function readSomeFile(filename, cb) {
  fs.readFile(
    filename,
    'utf8',
    d.intercept(data => {
      // Note, o primeiro argumento nunca é passado para o
      // callback, pois presume-se que seja o argumento 'Error'
      // e, portanto, interceptado pelo domínio.

      // Se isso lançar um erro, ele também será passado para o domínio
      // para que a lógica de tratamento de erros possa ser movida para o evento 'error'
      // no domínio em vez de ser repetida em todo o programa.
      return cb(null, JSON.parse(data))
    })
  )
}

d.on('error', er => {
  // Ocorreu um erro em algum lugar. Se lançarmos agora, isso irá travar o programa
  // com o número de linha normal e a mensagem de pilha.
})

domain.remove(emitter)

O oposto de domain.add(emitter). Remove o tratamento de domínio do emissor especificado.

domain.run(fn[, ...args])

Executa a função fornecida no contexto do domínio, vinculando implicitamente todos os emissores de eventos, temporizadores e solicitações de baixo nível que são criados nesse contexto. Opcionalmente, argumentos podem ser passados para a função.

Esta é a maneira mais básica de usar um domínio.

js
const domain = require('node:domain')
const fs = require('node:fs')
const d = domain.create()
d.on('error', er => {
  console.error('Erro capturado!', er)
})
d.run(() => {
  process.nextTick(() => {
    setTimeout(() => {
      // Simulação de algumas coisas assíncronas variadas
      fs.open('arquivo-inexistente', 'r', (er, fd) => {
        if (er) throw er
        // prosseguir...
      })
    }, 100)
  })
})

Neste exemplo, o manipulador d.on('error') será acionado, em vez de travar o programa.

Domínios e promessas

A partir do Node.js 8.0.0, os manipuladores de promessas são executados dentro do domínio no qual a chamada para .then() ou .catch() foi feita:

js
const d1 = domain.create()
const d2 = domain.create()

let p
d1.run(() => {
  p = Promise.resolve(42)
})

d2.run(() => {
  p.then(v => {
    // executando em d2
  })
})

Um retorno de chamada pode ser vinculado a um domínio específico usando domain.bind(callback):

js
const d1 = domain.create()
const d2 = domain.create()

let p
d1.run(() => {
  p = Promise.resolve(42)
})

d2.run(() => {
  p.then(
    p.domain.bind(v => {
      // executando em d1
    })
  )
})

Os domínios não interferirão nos mecanismos de tratamento de erros para promessas. Em outras palavras, nenhum evento 'error' será emitido para rejeições de Promise não tratadas.