Assíncrono visa aumentar a taxa de ocupação da CPU e mantê-la ocupada o tempo todo.
Algumas operações (a mais típica é a E/S) não requerem a participação da CPU e são muito demoradas. Se não for usado o assíncrono, formará um estado de bloqueio, fazendo com que a CPU fique ociosa e a página congele.
Quando uma operação de E/S ocorre em um ambiente assíncrono, a CPU deixa o trabalho de E/S de lado (neste momento, a E/S é assumida por outros controladores e os dados ainda estão sendo transmitidos) e então processa a próxima tarefa , aguardando a conclusão da operação de E/S Notifique a CPU (o retorno de chamada é um método de notificação) para voltar ao trabalho.
O conteúdo principal de "JavaScript assíncrono e retorno de chamada" é que o horário de término específico do trabalho assíncrono é incerto. Para executar com precisão o processamento subsequente após a conclusão do trabalho assíncrono, um retorno de chamada precisa ser passado para a função assíncrona, para que Depois. concluindo seu trabalho, continue com as tarefas a seguir.
Embora os retornos de chamada possam ser muito simples de implementar de forma assíncrona, eles podem formar um inferno de retorno de chamada devido ao aninhamento múltiplo. Para evitar o inferno do retorno de chamada, você precisa denestizar e alterar a programação aninhada para programação linear.
Promise
é a melhor solução para lidar com o inferno de retorno de chamada em JavaScript
.
Promise
ser traduzido como "promessa". Podemos encapsular o trabalho assíncrono e chamá-lo de Promise
, ou seja, fazer uma promessa e prometer dar um sinal claro após o término do trabalho assíncrono!
Sintaxe Promise
:
deixe promessa = new Promise(function(resolve,reject){ // Trabalho assíncrono})
Através da sintaxe acima, podemos encapsular o trabalho assíncrono em uma Promise
. A função passada ao criar Promise
é o método para lidar com o trabalho assíncrono, também conhecido como executor
.
resolve
e reject
são funções de retorno de chamada fornecidas pelo próprio JavaScript
. Elas podem ser chamadas quando executor
conclui a tarefa:
resolve(result)
- se for concluída com sucesso, result
será retornadoreject(error)
- se a execução falhar e error
erro será retornado; error
será gerado;executor
será executado automaticamente imediatamente após Promise
ser criada, e seu status de execução alterará o estado das propriedades internas Promise
:
state
- inicialmente pending
e depois convertido para fulfilled
após resolve
ser chamada ou rejected
quando reject
é chamado;result
—— undefined
inicialmente e então se torna value
após resolve(value)
ser chamado ou se torna error
após reject
ser chamadofs.readFile
uma função assíncrona
. Podemos passá-lo no executor
As operações de leitura do arquivo são realizadas no arquivo, encapsulando assim o trabalho assíncrono.
O código a seguir encapsula a função fs.readFile
e usa resolve(data)
para lidar com resultados bem-sucedidos e reject(err)
para lidar com resultados com falha.
O código é o seguinte:
deixe promessa = new Promise((resolver, rejeitar) => { fs.readFile('1.txt', (erro, dados) => { console.log('Ler 1.txt') se (errar) rejeitar (errar) resolver (dados) })})
Se executarmos este código, as palavras "Read 1.txt" serão exibidas, comprovando que a operação de leitura do arquivo é realizada imediatamente após a criação Promise
.
Promise
geralmente encapsula código assíncrono internamente, mas não encapsula apenas código assíncrono.
O caso Promise
acima encapsula a operação de leitura do arquivo. O arquivo será lido imediatamente após a conclusão da criação. Se você deseja obter o resultado da execução Promise
, você precisa usar três métodos then
, catch
e finally
.
O método then
do Promise
pode ser usado para lidar com o trabalho após Promise
ser concluída. Ele recebe dois parâmetros de retorno de chamada. A sintaxe é a seguinte:
promessa.then(função(resultado),função(erro))
result
é o valor recebido por resolve
;error
é o parâmetro recebido reject
rejeitar
; promessa = nova Promessa((resolver, rejeitar) => { fs.readFile('1.txt', (erro, dados) => { console.log('Ler 1.txt') se (errar) rejeitar (errar) resolver (dados) })})promessa.então( (dados) => { console.log('Executado com sucesso, o resultado é' + data.toString()) }, (erro) => { console.log('Falha na execução, erro é' + err.message) })
Se a leitura do arquivo for executada com sucesso, a primeira função será chamada:
PS E:CodeNodedemos 3-callback> node .index.js Leia 1.txt Se executada com sucesso, o resultado é 1
excluído 1.txt
. Se a execução falhar, a segunda função será chamada:
PS E:CodeNodedemos 3-callback> node .index.js. Leia 1.txt A execução falhou com o erro ENOENT: nenhum arquivo ou diretório, abra 'E:CodeNodedemos 3-callback1.txt'
Se focarmos apenas no resultado da execução bem-sucedida, podemos passar apenas um função de retorno de chamada:
promessa .then((data)=>{ console.log('Executado com sucesso, o resultado é' + data.toString())})
Neste ponto implementamos uma operação de leitura assíncrona do arquivo.
Se nos concentrarmos apenas no resultado da falha, podemos passar null
para o primeiro then
de chamada: promise.then(null,(err)=>{...})
.
Ou use uma maneira mais elegante: promise.catch((err)=>{...})
let promessa = new Promise((resolve, rejeitar) => { fs.readFile('1.txt', (erro, dados) => { console.log('Ler 1.txt') se (errar) rejeitar (errar) resolver (dados) })})promise.catch((err)=>{ console.log(err.message)})
.catch((err)=>{...})
e then(null,(err)=>{...})
têm exatamente o mesmo efeito.
.finally
é uma função que será executada independentemente do resultado da promise
. Ela tem o mesmo propósito que finally
na sintaxe try...catch...
e pode lidar com operações não relacionadas ao resultado.
Por exemplo:
new Promise((resolver,rejeitar)=>{ //alguma coisa...}).finalmente(()=>{console.log('Executar independentemente do resultado')}).then(resultado=>{...}, err=>{...} )O retorno de chamada final não tem parâmetros
fs.readFile()
ler 10 arquivos sequencialmente e gerar o conteúdo. dos dez arquivos sequencialmente.
Como o próprio fs.readFile()
é assíncrono, devemos usar o aninhamento de retorno de chamada. O código é o seguinte:
fs.readFile('1.txt', (err, data) => {. console.log(data.toString()) //1 fs.readFile('2.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('3.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('4.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('5.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('6.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('7.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('8.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('9.txt', (erro, dados) => { console.log(dados.toString()) fs.readFile('10.txt', (erro, dados) => { console.log(dados.toString()) // ==> Portão do Inferno}) }) }) }) }) }) }) }) })})
Embora o código acima possa completar a tarefa, à medida que o aninhamento de chamadas aumenta, o nível do código se torna mais profundo e a dificuldade de manutenção aumenta, especialmente quando estamos usando código real que pode conter muitos loops e instruções condicionais, em vez do. simples console.log(...)
no exemplo.
Se não usarmos retornos de chamada e chamarmos fs.readFile()
diretamente em sequência de acordo com o código a seguir, o que acontecerá?
//Nota: Esta é a maneira errada de escrever fs.readFile('1.txt', (err, data) => { console.log(data.toString())})fs.readFile('2.txt', (err, dados) => { console.log(data.toString())})fs.readFile('3.txt', (err, dados) => { console.log(data.toString())})fs.readFile('4.txt', (err, dados) => { console.log(data.toString())})fs.readFile('5.txt', (err, dados) => { console.log(data.toString())})fs.readFile('6.txt', (err, dados) => { console.log(data.toString())})fs.readFile('7.txt', (err, dados) => { console.log(data.toString())})fs.readFile('8.txt', (err, dados) => { console.log(data.toString())})fs.readFile('9.txt', (err, dados) => { console.log(data.toString())})fs.readFile('10.txt', (err, dados) => { console.log(data.toString())})
A seguir estão os resultados do meu teste (os resultados de cada execução são diferentes):
PS E:CodeNodedemos 3-callback> node .index.O motivo pelo qual
js12346957108
produz esse resultado não sequencial é assíncrono e não paralelismo multithread. Assíncrono pode ser alcançado em um único thread.
A razão pela qual este caso de erro é usado aqui é para enfatizar o conceito de assincronidade. Se você não entende por que esse resultado ocorre, você deve voltar e compensar a lição!
A ideia de usar Promise
para resolver a leitura sequencial assíncrona de arquivos:
promise2
promise1
e usar resolve
para retornar o resultadopromise1.then
para receber e gerar o resultado da leitura do arquivo.promise1.then
e retornepromise2.then
para receber e gerarpromise3.then
promise2.then
promise3
. o código é o seguinte:
deixe promessa1 = new Promise( (resolver, rejeitar) => { fs.readFile('1.txt', (erro, dados) => { se (errar) rejeitar (errar) resolver (dados) })})deixe promessa2 = promessa1.então( dados => { console.log(dados.toString()) retornar nova Promessa((resolver, rejeitar) => { fs.readFile('2.txt', (erro, dados) => { se (errar) rejeitar (errar) resolver (dados) }) }) })deixe promessa3 = promessa2.então( dados => { console.log(dados.toString()) retornar nova Promessa((resolver, rejeitar) => { fs.readFile('3.txt', (erro, dados) => { se (errar) rejeitar (errar) resolver (dados) }) }) })deixe promessa4 = promessa3.então( dados => { console.log(dados.toString()) //..... })... ...
Desta forma, escrevemos o inferno de retorno de chamada aninhado original em um modo linear.
Mas ainda há um problema com o código. Embora o código tenha ficado mais bonito em termos de gerenciamento, ele aumenta muito o comprimento do código.
O código acima é muito longo. Podemos reduzir a quantidade de código por meio de duas etapas:
promise
intermediária e vincular o .then
codifique da seguinte forma:
function myReadFile (caminho) { retornar nova Promessa((resolver, rejeitar) => { fs.readFile(caminho, (erro, dados) => { se (errar) rejeitar (errar) console.log(dados.toString()) resolver() }) })}meuReadFile('1.txt') .then(dados => { return meuReadFile('2.txt') }) .then(dados => { return meuReadFile('3.txt') }) .then(dados => { return meuReadFile('4.txt') }) .then(dados => { return meuReadFile('5.txt') }) .then(dados => { return meuReadFile('6.txt') }) .then(dados => { return meuReadFile('7.txt') }) .then(dados => { return meuReadFile('8.txt') }) .then(dados => { return meuReadFile('9.txt') }) .then(data => { return myReadFile('10.txt') })
Como o método myReadFile
retornará uma nova Promise
, podemos executar diretamente o método .then
. Este método de programação é chamado de programação em cadeia .
O resultado da execução do código é o seguinte:
PS E:CodeNodedemos 3-callback> node .index.js12345678910
Isso conclui a operação de leitura de arquivo assíncrona e sequencial.
Nota: Um novo objeto
Promise
deve ser retornado no método.then
de cada etapa, caso contrário o antigoPromise
anterior será recebido.Isso ocorre porque cada método
then
continuará a transmitir suaPromise
para baixo.