É justo dizer que promessas são apenas açúcar sintático. Tudo o que você pode fazer com promessas que você pode fazer com retornos de chamada. De fato, a maioria das implementações promissoras fornece formas de conversão entre as duas quando você quiser.
A razão profunda pela qual as promessas costumam ser melhores é que elas são mais compostáveis , o que significa aproximadamente que combinar várias promessas "simplesmente funciona", enquanto a combinação de vários retornos de chamada geralmente não funciona. Por exemplo, é trivial atribuir uma promessa a uma variável e anexar manipuladores adicionais a ela mais tarde, ou até anexar um manipulador a um grande grupo de promessas que são executadas somente após todas as promessas serem resolvidas. Embora você possa emular essas coisas com retornos de chamada, é preciso muito mais código, é muito difícil de executar corretamente, e o resultado final geralmente é muito menos sustentável.
Uma das maiores (e mais sutis) maneiras pelas quais as promessas ganham sua composição é através do tratamento uniforme dos valores de retorno e das exceções não capturadas. Com os retornos de chamada, a maneira como uma exceção é tratada pode depender inteiramente de qual dos muitos retornos de chamada aninhados a lançou e qual das funções que recebem retornos de chamada tem uma tentativa / captura em sua implementação. Com promessas, você sabe que uma exceção que escapa de uma função de retorno de chamada será capturada e transmitida ao manipulador de erros que você forneceu .error()
ou .catch()
.
Para o exemplo que você deu de um único retorno de chamada versus uma única promessa, é verdade que não há diferença significativa. É quando você tem um zilhão de retornos de chamada versus um zilhão de promessas que o código baseado em promessas tende a parecer muito melhor.
Aqui está uma tentativa de algum código hipotético escrito com promessas e, em seguida, com retornos de chamada que devem ser complexos o suficiente para lhe dar uma idéia do que estou falando.
Com promessas:
createViewFilePage(fileDescriptor) {
getCurrentUser().then(function(user) {
return isUserAuthorizedFor(user.id, VIEW_RESOURCE, fileDescriptor.id);
}).then(function(isAuthorized) {
if(!isAuthorized) {
throw new Error('User not authorized to view this resource.'); // gets handled by the catch() at the end
}
return Promise.all([
loadUserFile(fileDescriptor.id),
getFileDownloadCount(fileDescriptor.id),
getCommentsOnFile(fileDescriptor.id),
]);
}).then(function(fileData) {
var fileContents = fileData[0];
var fileDownloads = fileData[1];
var fileComments = fileData[2];
fileTextAreaWidget.text = fileContents.toString();
commentsTextAreaWidget.text = fileComments.map(function(c) { return c.toString(); }).join('\n');
downloadCounter.value = fileDownloads;
if(fileDownloads > 100 || fileComments.length > 10) {
hotnessIndicator.visible = true;
}
}).catch(showAndLogErrorMessage);
}
Com retornos de chamada:
createViewFilePage(fileDescriptor) {
setupWidgets(fileContents, fileDownloads, fileComments) {
fileTextAreaWidget.text = fileContents.toString();
commentsTextAreaWidget.text = fileComments.map(function(c) { return c.toString(); }).join('\n');
downloadCounter.value = fileDownloads;
if(fileDownloads > 100 || fileComments.length > 10) {
hotnessIndicator.visible = true;
}
}
getCurrentUser(function(error, user) {
if(error) { showAndLogErrorMessage(error); return; }
isUserAuthorizedFor(user.id, VIEW_RESOURCE, fileDescriptor.id, function(error, isAuthorized) {
if(error) { showAndLogErrorMessage(error); return; }
if(!isAuthorized) {
throw new Error('User not authorized to view this resource.'); // gets silently ignored, maybe?
}
var fileContents, fileDownloads, fileComments;
loadUserFile(fileDescriptor.id, function(error, result) {
if(error) { showAndLogErrorMessage(error); return; }
fileContents = result;
if(!!fileContents && !!fileDownloads && !!fileComments) {
setupWidgets(fileContents, fileDownloads, fileComments);
}
});
getFileDownloadCount(fileDescriptor.id, function(error, result) {
if(error) { showAndLogErrorMessage(error); return; }
fileDownloads = result;
if(!!fileContents && !!fileDownloads && !!fileComments) {
setupWidgets(fileContents, fileDownloads, fileComments);
}
});
getCommentsOnFile(fileDescriptor.id, function(error, result) {
if(error) { showAndLogErrorMessage(error); return; }
fileComments = result;
if(!!fileContents && !!fileDownloads && !!fileComments) {
setupWidgets(fileContents, fileDownloads, fileComments);
}
});
});
});
}
Pode haver algumas maneiras inteligentes de reduzir a duplicação de código na versão de retorno de chamada, mesmo sem promessas, mas todas as que posso pensar se resumem a implementar algo muito parecido com promessa.