Este artigo é uma compreensão pessoal do nodejs no desenvolvimento e aprendizado real. Agora está compilado para referência futura. Ficaria honrado se pudesse inspirar você.
E/S : Entrada/Saída, a entrada e a saída de um sistema.
Um sistema pode ser entendido como um indivíduo, como uma pessoa. Quando você fala, é a saída e, quando você escuta, é a entrada.
A diferença entre E/S com bloqueio e E/S sem bloqueio reside em se o sistema pode receber outra entrada durante o período entre a entrada e a saída .
Aqui estão dois exemplos para ilustrar o que são E/S bloqueadoras e E/S não bloqueadoras:
1. Cozinhando
Em primeiro lugar, precisamos determinar o escopo de um sistema. Neste exemplo, a tia da cantina e o garçom do restaurante são considerados um sistema. A entrada é o pedido e a saída é o serviço de pratos .
Então, se você pode aceitar pedidos de outras pessoas entre pedir e servir comida, você pode determinar se está bloqueando a E/S ou não bloqueando a E/S.
Já a tia do refeitório não pode fazer pedidos para outros alunos no momento do pedido. Somente depois que o aluno terminar de fazer o pedido e servir os pratos, ela poderá aceitar o pedido do próximo aluno, então a tia do refeitório estará bloqueando a E/S.
Para um garçom de restaurante, ele pode servir o próximo hóspede após o pedido e antes que o hóspede sirva o prato, de modo que o garçom tenha E/S sem bloqueio.
2. Faça tarefas domésticas
Ao lavar roupas, você não precisa esperar a máquina de lavar. Você pode varrer o chão e arrumar a mesa neste momento. Depois de arrumar a mesa, as roupas são lavadas e você pode pendurá-las para secar. 25 minutos no total.
A lavanderia é, na verdade, uma E/S sem bloqueio. Você pode fazer outras coisas entre colocar as roupas na máquina de lavar e terminar a lavagem.
A razão pela qual a E/S sem bloqueio pode melhorar o desempenho é que ela pode evitar esperas desnecessárias.
A chave para entender a E/S sem bloqueio é
Como a E/S sem bloqueio de nodejs é refletida? Conforme mencionado anteriormente, um ponto importante na compreensão da E/S sem bloqueio é primeiro determinar um limite do sistema. O limite do sistema do nó é o thread principal .
Se o diagrama de arquitetura abaixo for dividido de acordo com a manutenção do thread, a linha pontilhada à esquerda é o thread nodejs e a linha pontilhada à direita é o thread C++.
Agora, o thread nodejs precisa consultar o banco de dados. Esta é uma operação típica de E/S. Ela não esperará pelos resultados da E/S e continuará a processar outras operações. Ela distribuirá uma grande quantidade de poder de computação para outro C++. tópicos para cálculos.
Espere até que o resultado saia e retorne-o ao thread nodejs. Antes de obter o resultado, o thread nodejs também pode realizar outras operações de E/S, portanto, não é bloqueador.
O thread nodejs é equivalente à parte esquerda ser o garçom, e o thread c++ é o chef.
Portanto, a E/S sem bloqueio do nó é concluída chamando threads de trabalho C++.
Então, como notificar o thread nodejs quando o thread c++ obtém o resultado? A resposta é orientada por eventos .
Bloqueio: o processo dorme durante a E/S e aguarda a conclusão da E/S antes de prosseguir para a próxima etapa
sem bloqueio : a função retorna imediatamente durante a E/S e o processo não espera pela E/S; Ó para completar.
Então, para saber o resultado retornado, você precisa usar o event driver .
O chamado evento orientado a eventos pode ser entendido como o mesmo que o evento de clique do front-end. Primeiro escrevo um evento de clique, mas não sei quando ele será acionado. Somente quando for acionado, o thread principal será. executar a função orientada a eventos.
Este modo também é um modo observador, ou seja, primeiro ouço o evento e depois o executo quando ele é acionado.
Então, como implementar a movimentação de eventos? A resposta é programação assíncrona .
Como mencionado acima, o nodejs possui um grande número de E/S sem bloqueio, portanto, os resultados da E/S sem bloqueio precisam ser obtidos por meio de funções de retorno de chamada. Este método de uso de funções de retorno de chamada é a programação assíncrona . Por exemplo, o código a seguir obtém o resultado por meio da função de retorno de chamada:
glob(__dirname+'/**/*', (err, res) => { resultado = res console.log('obter resultado') })
O primeiro parâmetro da função de retorno de chamada do nodejs é erro e os parâmetros subsequentes são o resultado . Por que fazer isso?
tentar { entrevista(função(){ console.log('sorriso') }) } pegar(errar) { console.log('chorar', errar) } função entrevista(retorno de chamada) { setTimeout(() => { if(Math.random() <0,1) { retorno de chamada('sucesso') } outro { lançar novo erro('falha') } }, 500) }
Após a execução, ele não foi detectado e o erro foi lançado globalmente, fazendo com que todo o programa nodejs travasse.
Ele não é capturado por try catch porque setTimeout reabre o loop de eventos. Cada vez que um loop de eventos é aberto, um contexto de pilha de chamadas é regenerado e pertence à pilha de chamadas do loop de eventos anterior. o contexto da pilha de chamadas Tudo é diferente. Não há try catch nesta nova pilha de chamadas, portanto, o erro é lançado globalmente e não pode ser detectado. Para obter detalhes, consulte este artigo Problemas ao executar try catch na fila assíncrona.
Então, o que fazer? Passe o erro como parâmetro:
function entrevista(callback) { setTimeout(() => { if(Math.random() <0,5) { retorno de chamada('sucesso') } outro { retorno de chamada(novo erro('falha')) } }, 500) } entrevista(função (res) { if (res instância de erro) { console.log('choro') retornar } console.log('sorriso') })
Mas isso é mais problemático e você tem que fazer um julgamento no retorno de chamada, então existe uma regra madura. O primeiro parâmetro é err.
função entrevista(retorno de chamada) { setTimeout(() => { if(Math.random() <0,5) { retorno de chamada(nulo, 'sucesso') } outro { retorno de chamada(novo erro('falha')) } }, 500) } entrevista(função (res) { se (res) { retornar } console.log('sorriso') })O método de gravação de retorno de chamada do nodejs de
não apenas causará a área de retorno de chamada, mas também causará o problema de controle de processo assíncrono .
O controle de processo assíncrono refere-se principalmente a como lidar com a lógica de simultaneidade quando ocorre simultaneidade. Ainda usando o exemplo acima, se o seu colega entrevistar duas empresas, ele não será entrevistado pela terceira empresa até que entreviste duas empresas com sucesso. Você precisa adicionar uma variável count globalmente:
var count = 0 entrevista((err) => { se (errar) { retornar } contar++ if (contagem >= 2) { //Lógica de processamento} }) entrevista((err) => { se (errar) { retornar } contar++ if (contagem >= 2) { //Lógica de processamento} })
Escrever como acima é muito problemático e feio. Portanto, os métodos de escrita de promessa e async/await apareceram mais tarde.
que o loop de eventos atual não pode obter o resultado, mas o loop de eventos futuro fornecerá o resultado. É muito parecido com o que um canalha diria.
A promessa não é apenas um canalha, mas também uma máquina de estados:
const pro = new Promise((resolve, rejeitar) => { setTimeout(() => { resolver('2') }, 200) }) console.log(pro) // Imprimir: Promise { <pending> }
Executar then ou catch retornará uma nova promessa . O estado final da promessa é determinado pelos resultados da execução das funções de retorno de chamada de then e catch:
função entrevista() { retornar nova Promessa((resolver, rejeitar) => { setTimeout(() => { se (Math.random() > 0,5) { resolver('sucesso') } outro { rejeitar(novo erro('falha')) } }) }) } var promessa = entrevista() var promessa1 = promessa.then(() => { retornar nova Promessa((resolver, rejeitar) => { setTimeout(() => { resolver('aceitar') }, 400) }) })
O estado da promessa1 é determinado pelo estado da promessa em retorno, ou seja, o estado da promessa1 após a promessa em retorno ser executada. Quais são os benefícios disso? Isso resolve o problema do inferno de retorno de chamada .
var promessa = entrevista() .então(() => { entrevista de retorno() }) .então(() => { entrevista de retorno() }) .então(() => { entrevista de retorno() }) .catch(e => { console.log(e) })
Então, se o status da promessa retornada for rejeitado, o primeiro catch será chamado e o subsequente então não será chamado. Lembre-se: as chamadas rejeitadas são as primeiras capturas e as chamadas resolvidas são as primeiras.
Se a promessa é apenas para resolver retornos de chamada infernais, é muito pequena para subestimar a promessa. A principal função da promessa é resolver o problema de controle de processo assíncrono. Se você quiser entrevistar duas empresas ao mesmo tempo:
function entrevista() { retornar nova Promessa((resolver, rejeitar) => { setTimeout(() => { se (Math.random() > 0,5) { resolver('sucesso') } outro { rejeitar(novo erro('falha')) } }) }) } promessa .all([entrevista(), entrevista()]) .então(() => { console.log('sorriso') }) // Se uma empresa rejeitar, pegue-a .catch(() => { console.log('choro') })
O que exatamente é sincronizar/await:
console.log(async function() { retornar 4 }) console.log(função(){ retornar nova Promessa((resolver, rejeitar) => { resolver (4) }) })
O resultado impresso é o mesmo, ou seja, async/await é apenas um açúcar sintático para promessa.
Sabemos que try catch captura erros com base na pilha de chamadas e só pode capturar erros acima da pilha de chamadas. Mas se você usar aguardar, poderá detectar erros em todas as funções na pilha de chamadas. Mesmo que o erro seja lançado na pilha de chamadas de outro loop de eventos, como setTimeout.
Depois de transformar o código da entrevista, você pode ver que o código foi bastante simplificado.
tentar { aguardar entrevista(1) aguardar entrevista(2) aguardar entrevista(2) } pegar(e => { console.log(e) })
E se for uma tarefa paralela?
await Promise.all([interview(1), Interview(2)])
Por causa da E/S sem bloqueio do nodejs, é necessário usar métodos orientados a eventos para obter resultados de E/S para alcançar o evento. Para obter resultados, você deve usar programação assíncrona, como funções de retorno de chamada. Então, como executar essas funções de retorno de chamada para obter os resultados? Então você precisa usar um loop de eventos.
O loop de eventos é a base principal para realizar a função de E/S sem bloqueio do nodejs. E/S sem bloqueio e loop de eventos são recursos fornecidos pela biblioteca C++ libuv
.
Demonstração de código:
const eventloop = { fila: [], laço() { while(this.queue.length) { retorno de chamada const = this.queue.shift() ligar de volta() } setTimeout(this.loop.bind(this), 50) }, adicionar(retorno de chamada) { this.queue.push(retorno de chamada) } } eventloop.loop() setTimeout(() => { eventloop.add(() => { console.log('1') }) }, 500) setTimeout(() => { eventloop.add(() => { console.log('2') }) }, 800)
setTimeout(this.loop.bind(this), 50)
garante que após 50ms, ele irá verificar se há um retorno de chamada na fila e, em caso afirmativo, executá-lo. Isso forma um loop de eventos.
Claro, os eventos reais são muito mais complicados e há mais de uma fila. Por exemplo, há uma fila de operação de arquivo e uma fila de tempo.
const eventloop={ fila: [], fsQueue: [], timerQueue: [], laço() { while(this.queue.length) { retorno de chamada const = this.queue.shift() ligar de volta() } this.fsQueue.forEach(retorno de chamada => { se (concluído) { ligar de volta() } }) setTimeout(this.loop.bind(this), 50) }, adicionar(retorno de chamada) { this.queue.push(retorno de chamada) } }
Em primeiro lugar, entendemos o que é E/S sem bloqueio, ou seja, pular imediatamente a execução de tarefas subsequentes ao encontrar E/S e não esperar pelo resultado da E/S. Quando a E/S for processada, a função de processamento de eventos que registramos será chamada, que é chamada de orientada a eventos. A programação assíncrona é necessária para implementar a unidade de evento. A programação assíncrona é o link mais importante no nodejs. Ela vai da função de retorno de chamada à promessa e, finalmente, ao assíncrono/espera (usando o método síncrono para escrever lógica assíncrona).