Node é single threaded, istoé, ele é incapaz de executar duas coisas ao mesmo tempo, com exceção de operaçes de IO.
Exemplos de operação não bloqueantes: Acesso à rede, acesso à arquivos etc.
Exemplos de açes bloqueantes: Loop num array, cálculos complexos etc.
Em node, o padrão é toda função assíncrona ter uma versão síncrona, por exemplo fs.readFile
e fs.readFileSync
.
As verses síncronas são úteis para aplicações que não dependem de concorrência, como scripts e programas de linha de comando por exemplo.
Via de regra usamos a versão assíncrona. Em uma API, por exemplo, qualquer operação bloqueante impede que novas requisiçes sejam processadas pelo servidor, por isso é importante sempre fazer chamadas não bloqueantes.
Exemplo de chamadas não-bloqueante e bloqueante:
var fs = require('fs');
// não-bloqueante
fs.readFile('filename', 'utf8', function(err, contents) {
if (err) {
console.log(err);
} else {
console.log(contents);
}
});
// bloqueante!
try {
const contents = fs.readFileSync('filename', 'utf8');
} catch (err) {
console.log(err);
}
Em Node existe um padrão para chamadas assíncronas. A função sempre recebe um callback como último parâmetro, e este callback tem como primeiro parâmetro o erro ocorrido. Caso nenhum erro tenha ocorrido, o valor desse parâmetro é null. Vide fs.readFile
.
Como isso é um padrão, a biblioteca padrão do Node tem uma função que converte essa chamada de callback em promise:
const util = require('util')
const readFilePromisified = util.promisify(fs.readFile);
readFilePromisified('filename', 'utf8')
.then(contents => console.log(contents))
.catch(err => console.log(err));
O uso exagerado de callbacks dificulta a leitura do código.
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename);
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err);
} else {
console.log(filename + ' : ' + values);
aspect = (values.width / values.height);
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect);
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err);
});
}.bind(this));
}
});
});
}
});
Wikipedia:
In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is yet incomplete.
Promises têm o mesmo papel de callbacks, isto é, elas também executam uma função quando uma operação termina. A diferença é que no caso de callbacks, a mesma função será executada em caso de sucesso ou falha, já nas promises é possível ter uma função pra cada resultado.
function successIfOdd(value) {
return new Promise((resolve, reject) => {
if (value % 2 == 0) {
resolve(value);
} else {
reject(value);
}
});
}
successIfOdd(1).then(value => console.log(value)); // 1
successIfOdd(2).catch(value => console.log(value)); // 2
Promises podem estar em um de três estados: pending
, rejected
ou fulfilled
.
- Pending: A promise não foi resolvida nem rejeitada.
- Rejected: A promise foi rejeitada.
- Fulfilled: A promise foi resolvida.
O callback do método then
só é chamado quando uma promise é resolvida (resolve
).
O callback do método catch
é chamado quando uma promise é rejeitada (reject
), ou quando ocorre algum erro não capturado dentro da promise.
function foo() {
return new Promise((resolve, reject) => resolve()); // cairá no `then`
return new Promise((resolve, reject) => reject()); // cairá no `catch`
return new Promise((resolve, reject) => { throw new Error('Oops'); }); // cairá no `catch`
}
Uma vez que um erro foi capturado no catch
, você pode encadear outros then
.
function foo() {
return new Promise((resolve, reject) => {
reject('Oops');
});
}
foo()
.catch(() => console.log('Deu m*'))
.then(() => console.log('Isso será printado =)'))
.then(() => { throw new Error('😱'); })
.catch(err => console.log(err))
.then(() => console.log('Como o erro foi capturado, irei executar! 😁'));
É possível transformar dados no encadeamento de then
:
function someApiCallResult() {
return new Promise((resolve, reject) => {
resolve('{"foo":1}');
});
}
someApiCallResult()
.then(res => JSON.parse(res))
.then(json => ({ bar: 2, ...json }))
.then(console.log); // {bar: 2, foo: 1}
Em alguns casos é útil resolver ou rejeitar uma promise imediatamente (em testes automatizados por exemplo):
function foo() {
return new Promise((resolve, reject) => {
resolve('value'); // ou reject('value');
});
}
// ou
function foo() {
return Promise.resolve('value'); // ou Promise.reject('value')
}
Eventualmente é útil executar diversas promises em "paralelo" e aguardar todas finalizarem para proseguie com a lógica do código. Um caso típico é quando se deseja fazer vários insert
no banco de dados. Pra isso ns utilizados Promise.all
.
function foo() {
return new Promise(resolve => {
setTimeout(() => resolve({ foo: 1 }), 200);
});
}
function bar() {
return new Promise(resolve => {
setTimeout(() => resolve({ bar: 2 }), 400);
});
}
Promise.all([
foo(),
bar(),
]).then(([ fooResult, barResult ]) => {
console.log(fooResult);
console.log(barResult);
});
Assim como em qualquer promise, se uma das promises forem rejeitadas, somente o catch
ser chamado. A primeira a ser rejeitada enviará seu erro pro catch
.
function foo() {
return Promise.resolve('Yay');
}
function bar() {
return Promise.reject('Eita');
}
Promise.all([
foo(),
bar(),
]).then(() => console.log('Nunca serei executado =('))
.catch(err => console.log(err)); // Eita
Em outros casos somente estamos interessados em saber quando a primeira promise terminar. Nesse caso usamos Promise.race
:
function foo() {
return new Promise(resolve => {
setTimeout(() => resolve('Poxa'), 150);
});
}
function bar() {
return new Promise(resolve => {
setTimeout(() => resolve('Vida'), 100);
});
}
Promise.race([
foo(),
bar(),
]).then(console.log); // 'Vida'
Quando uma promise retorna outra promise, o resultado final é o resolvido pela última promise.
function foo() {
return Promise.resolve({ foo: 1 });
}
function bar() {
return Promise.resolve({ bar: 2 });
}
function baz() {
return foo().then(fooResult => {
return bar().then(barResult => {
return { baz: 3, ...barResult, ...fooResult };
});
});
}
baz().then(console.log); // { baz: 3, bar: 2, foo: 1 }
Async/await SEMPRE é usado em conjunto com promises.
Regras:
- Se uma função faz uso de
await
, ela precisa ter a keywordasync
antes de sua definição. - Se uma função retorna uma promise, você pode dar
await
nela.
function foo() {
return Promise.resolve({ foo: 1 });
}
async function bar() {
const result = await foo();
console.log(result);
}
bar(); // { foo: 1 }
Se a promise for rejeitada, ela levantará um erro se você estiver usando await
, então é necessário encapsular o trecho de código num bloco try/catch
.
function foo() {
return Promise.reject({ some: 'error' });
}
async function bar() {
try {
await foo();
} catch (e) {
console.log(e);
}
}
bar(); // { some: 'error' }
Para executar várias promises em "paralelo" usando async/await
, ainda é possvel usar Promise.all
, visto que essa função retorna uma promise.
function foo() {
return Promise.resolve({ poxa: 'vida' });
}
function bar() {
return Promise.resolve({ hein: 'wow' });
}
async function baz() {
const [fooResult, barResult] = await Promise.all([foo(), bar()]);
console.log(fooResult); // { poxa: 'vida' }
console.log(barResult); // { hein: 'wow' }
}
baz();
Curso básico de promise: https://br.udacity.com/course/javascript-promises--ud898