
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 .
Promisegeralmente 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 rejectrejeitar
; 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:
promise2promise1 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.thenpromise2.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 .thencodifique 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
Promisedeve ser retornado no método.thende cada etapa, caso contrário o antigoPromiseanterior será recebido.Isso ocorre porque cada método
thencontinuará a transmitir suaPromisepara baixo.