Resumo : neste tutorial, você aprenderá sobre funções de retorno de chamada JavaScript, incluindo retornos de chamada síncronos e assíncronos.
O que são retornos de chamada
Em JavaScript, as funções são cidadãs de primeira classe . Portanto, você pode passar uma função para outra função como argumento.
Por definição, um retorno de chamada é uma função que você passa para outra função como argumento para execução posterior.
O seguinte define uma filter()
função que aceita um array de números e retorna um novo array de números ímpares:
function filter(numbers) {
let results = [];
for (const number of numbers) {
if (number % 2 != 0) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers));
Linguagem de código: JavaScript ( javascript )
Como funciona.
- Primeiro, defina a
filter()
função que aceita um array de números e retorna um novo array de números ímpares. - Em segundo lugar, defina a
numbers
matriz que contém números pares e ímpares. - Terceiro, chame a
filter()
função para obter os números ímpares da matriz de números e gerar o resultado.
Se quiser retornar um array que contém números pares, você precisa modificar a filter()
função. Para tornar a filter()
função mais genérica e reutilizável, você pode:
- Primeiro, extraia a lógica do
if
bloco e envolva-a em uma função separada. - Segundo, passe a função para a função filter() como argumento.
Aqui está o código atualizado:
function isOdd(number) {
return number % 2 != 0;
}
function filter(numbers, fn) {
let results = [];
for (const number of numbers) {
if (fn(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers, isOdd));
Linguagem de código: JavaScript ( javascript )
O resultado é o mesmo. Entretanto, você pode passar qualquer função que aceite um argumento e retorne um valor booleano para o segundo argumento da filter()
função.
Por exemplo, você pode usar a filter()
função para retornar uma matriz de números pares como esta:
function isOdd(number) {
return number % 2 != 0;
}
function isEven(number) {
return number % 2 == 0;
}
function filter(numbers, fn) {
let results = [];
for (const number of numbers) {
if (fn(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers, isOdd));
console.log(filter(numbers, isEven));
Linguagem de código: JavaScript ( javascript )
Por definição, isOdd
e isEven
são funções de retorno de chamada ou retornos de chamada. Como a filter()
função aceita uma função como argumento, ela é chamada de função de ordem superior .
Um retorno de chamada pode ser uma função anônima, que é uma função sem nome como este:
function filter(numbers, callback) {
let results = [];
for (const number of numbers) {
if (callback(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
let oddNumbers = filter(numbers, function (number) {
return number % 2 != 0;
});
console.log(oddNumbers);
Linguagem de código: JavaScript ( javascript )
Neste exemplo, passamos uma função anônima para a filter()
função em vez de usar uma função separada.
No ES6, você pode usar uma função de seta como esta:
function filter(numbers, callback) {
let results = [];
for (const number of numbers) {
if (callback(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
let oddNumbers = filter(numbers, (number) => number % 2 != 0);
console.log(oddNumbers);
Linguagem de código: JavaScript ( javascript )
Existem dois tipos de retornos de chamada: retornos de chamada síncronos e assíncronos.
Retornos de chamada síncronos
Um retorno de chamada síncrono é executado durante a execução da função de ordem superior que usa o retorno de chamada. Os isOdd
e isEven
são exemplos de retornos de chamada síncronos porque são executados durante a execução da filter()
função.
Retornos de chamada assíncronos
Um retorno de chamada assíncrono é executado após a execução da função de ordem superior que usa o retorno de chamada.
Assincronicidade significa que se o JavaScript tiver que esperar a conclusão de uma operação, ele executará o restante do código enquanto espera.
Observe que JavaScript é uma linguagem de programação de thread único. Ele realiza operações assíncronas por meio da fila de retorno de chamada e do loop de eventos .
Suponha que você precise desenvolver um script que baixe uma imagem de um servidor remoto e a processe após a conclusão do download:
function download(url) {
// ...
}
function process(picture) {
// ...
}
download(url);
process(picture);
Linguagem de código: JavaScript ( javascript )
No entanto, o download de uma imagem de um servidor remoto leva tempo, dependendo da velocidade da rede e do tamanho da imagem.
A download()
função a seguir usa a setTimeout()
função para simular a solicitação de rede:
function download(url) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
},1000);
}
Linguagem de código: JavaScript ( javascript )
E este código emula a process()
função:
function process(picture) {
console.log(`Processing ${picture}`);
}
Linguagem de código: JavaScript ( javascript )
Quando você executa o seguinte código:
let url = 'https://tutorials.acervolima.com/pic.jpg';
download(url);
process(url);
Linguagem de código: JavaScript ( javascript )
você obterá a seguinte saída:
Processing https://tutorials.acervolima.com/pic.jpg
Downloading https://tutorials.acervolima.com/pic.jpg ...
Linguagem de código: JavaScript ( javascript )
Isso não é o que você esperava porque a process()
função é executada antes da download()
função. A sequência correta deve ser:
- Baixe a imagem e aguarde o download ser concluído.
- Processe a imagem.
Para resolver esse problema, você pode passar a process()
função para a download()
função e executar a process()
função dentro da download()
função assim que o download for concluído, assim:
function download(url, callback) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
// process the picture once it is completed
callback(url);
}, 1000);
}
function process(picture) {
console.log(`Processing ${picture}`);
}
let url = 'https://tutorials.acervolima.com/pic.jpg';
download(url, process);
Linguagem de código: JavaScript ( javascript )
Saída:
Downloading https://tutorials.acervolima.com/pic.jpg ...
Processing https://tutorials.acervolima.com/pic.jpg
Linguagem de código: JavaScript ( javascript )
Agora, funciona conforme o esperado.
Neste exemplo, process()
é um retorno de chamada passado para uma função assíncrona.
Quando você usa um retorno de chamada para continuar a execução do código após uma operação assíncrona, o retorno de chamada é chamado de retorno de chamada assíncrono.
Para tornar o código mais conciso, você pode definir a process()
função como anônima:
function download(url, callback) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
// process the picture once it is completed
callback(url);
}, 1000);
}
let url = 'https://tutorials.acervolima.com/pic.jpg';
download(url, function(picture) {
console.log(`Processing ${picture}`);
});
Linguagem de código: JavaScript ( javascript )
Tratamento de erros
A download()
função assume que tudo funciona bem e não considera nenhuma exceção. O código a seguir apresenta dois retornos de chamada: success
e failure
para lidar com os casos de sucesso e falha, respectivamente:
function download(url, success, failure) {
setTimeout(() => {
console.log(`Downloading the picture from ${url} ...`);
!url ? failure(url) : success(url);
}, 1000);
}
download(
'',
(url) => console.log(`Processing the picture ${url}`),
(url) => console.log(`The '${url}' is not valid`)
);
Linguagem de código: JavaScript ( javascript )
Aninhando retornos de chamada e a Pirâmide da Perdição
Como você baixa três imagens e as processa sequencialmente? Uma abordagem típica é chamar a download()
função dentro da função de retorno de chamada, assim:
function download(url, callback) {
setTimeout(() => {
console.log(`Downloading ${url} ...`);
callback(url);
}, 1000);
}
const url1 = 'https://tutorials.acervolima.com/pic1.jpg';
const url2 = 'https://tutorials.acervolima.com/pic2.jpg';
const url3 = 'https://tutorials.acervolima.com/pic3.jpg';
download(url1, function (url) {
console.log(`Processing ${url}`);
download(url2, function (url) {
console.log(`Processing ${url}`);
download(url3, function (url) {
console.log(`Processing ${url}`);
});
});
});
Linguagem de código: JavaScript ( javascript )
Saída:
Downloading https://tutorials.acervolima.com/pic1.jpg ...
Processing https://tutorials.acervolima.com/pic1.jpg
Downloading https://tutorials.acervolima.com/pic2.jpg ...
Processing https://tutorials.acervolima.com/pic2.jpg
Downloading https://tutorials.acervolima.com/pic3.jpg ...
Processing https://tutorials.acervolima.com/pic3.jpg
Linguagem de código: JavaScript ( javascript )
O script funciona perfeitamente bem.
No entanto, esta estratégia de retorno de chamada não se adapta bem quando a complexidade aumenta significativamente.
Aninhar muitas funções assíncronas dentro de retornos de chamada é conhecido como pirâmide da destruição ou inferno de retorno de chamada :
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
....
});
});
});
});
});
Linguagem de código: JavaScript ( javascript )
Para evitar a pirâmide da destruição, você usa promessas ou funções assíncronas/esperadas .
Resumo
- Um retorno de chamada é uma função passada para outra função como um argumento a ser executado posteriormente.
- Uma função de ordem superior é uma função que aceita outra função como argumento.
- As funções de retorno de chamada podem ser síncronas ou assíncronas.