É justo dizer que as 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 promessas de implementação fornece maneiras de converter entre os dois sempre que você quiser.
A razão profunda pela qual as promessas costumam ser melhores é que elas são mais compostáveis , o que significa que a combinação de várias promessas "apenas funciona", combinando vários retornos de chamada muitas vezes não. Por exemplo, é trivial atribuir uma promessa a uma variável e anexar manipuladores adicionais a ela mais tarde, ou até mesmo anexar um manipulador a um grande grupo de promessas executadas somente depois que todas as promessas forem resolvidas. Embora você possa emular essas coisas com callbacks, é preciso muito mais código, é muito difícil de fazer corretamente, e o resultado final geralmente é muito menos sustentável.
Uma das maiores (e mais sutis) maneiras pelas quais as promessas ganham sua composibilidade é por manipulação uniforme de valores de retorno e exceções não identificadas. Com os retornos de chamada, como uma exceção é manipulada pode depender inteiramente de quais dos muitos retornos de chamada aninhados a executaram, e quais das funções que usam retornos de chamada têm um try / catch 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 passada para o manipulador de erros fornecido com .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 promessa tende a parecer muito mais agradável.
Aqui está uma tentativa de algum código hipotético escrito com promessas e, em seguida, com callbacks que devem ser apenas 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 callbacks mesmo sem promessas, mas todas as que posso pensar se resumem à implementação de algo muito promissor.