Es justo decir que las promesas son solo azúcar sintáctica. Todo lo que puede hacer con las promesas que puede hacer con las devoluciones de llamada. De hecho, la mayoría de las implementaciones de promesa proporcionan formas de conversión entre las dos cuando quieras.
La razón profunda por la que las promesas a menudo son mejores es que son más compositivas , lo que significa que la combinación de múltiples promesas "simplemente funciona", mientras que la combinación de múltiples devoluciones de llamadas no es así. Por ejemplo, es trivial asignar una promesa a una variable y adjuntar controladores adicionales más adelante, o incluso adjuntar un controlador a un gran grupo de promesas que se ejecutan solo después de que se hayan resuelto todas las promesas. Si bien es posible emular estas cosas con devoluciones de llamada, se necesita mucho más código, es muy difícil de hacer correctamente y el resultado final suele ser mucho menos mantenible.
Una de las mayores (y más sutiles) formas en que las promesas obtienen su capacidad de composición es mediante el manejo uniforme de los valores de retorno y las excepciones no detectadas. Con las devoluciones de llamada, la forma en que se maneja una excepción puede depender completamente de cuál de las muchas devoluciones de llamadas anidadas se lanzó, y cuál de las funciones que toman devoluciones de llamada tiene un intento / captura en su implementación. Con las promesas, sabe que una excepción que escapa de una función de devolución de llamada se detectará y pasará al controlador de errores que proporcionó con .error()
o .catch()
.
Para el ejemplo que dio de una devolución de llamada única frente a una promesa única, es cierto que no hay una diferencia significativa. Es cuando tienes un millón de devoluciones de llamada frente a un millón de promesas que el código basado en la promesa tiende a verse mucho mejor.
Aquí se intenta un código hipotético escrito con promesas y luego con devoluciones de llamadas que deberían ser lo suficientemente complejas como para darte una idea de lo que estoy hablando.
Con Promesas:
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);
}
Con devoluciones de llamada:
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);
}
});
});
});
}
Puede haber algunas formas inteligentes de reducir la duplicación de código en la versión de devoluciones de llamada incluso sin promesas, pero todas las que puedo pensar se reducen a implementar algo muy prometedor.