Справедливо сказать, что обещания - это просто синтаксический сахар. Все, что вы можете сделать с обещаниями, вы можете сделать с обратными вызовами. На самом деле, большинство реализаций обещаний предоставляют способы преобразования между ними в любое время.
Глубокая причина, почему обещания часто лучше, состоит в том, что они более сочетаемы , что примерно означает, что объединение нескольких обещаний «просто работает», а объединение нескольких обратных вызовов часто - нет. Например, тривиально назначить обещание переменной и позже присоединить к нему дополнительные обработчики или даже присоединить обработчик к большой группе обещаний, которая выполняется только после разрешения всех обещаний. Хотя вы можете эмулировать эти вещи с помощью обратных вызовов, для этого требуется гораздо больше кода, его очень сложно сделать правильно, а конечный результат, как правило, гораздо менее обслуживаем.
Один из самых больших (и тончайших) способов получения обещаний их компоновки - это единообразная обработка возвращаемых значений и необработанных исключений. В случае обратных вызовов способ обработки исключения может полностью зависеть от того, какой из множества вложенных обратных вызовов его выбросил, и какая из функций, принимающих обратные вызовы, имеет реализацию try / catch. С обещаниями вы знаете, что исключение, которое выходит за пределы одной функции обратного вызова, будет перехвачено и передано в обработчик ошибок, предоставленный вами .error()
или .catch()
.
Для примера, который вы привели с одним обратным вызовом против одного обещания, это правда, что нет существенной разницы. Когда у вас есть миллионы обратных вызовов против тысячи обещаний, код, основанный на обещаниях, имеет тенденцию выглядеть намного лучше.
Вот попытка некоторого гипотетического кода, написанного с обещаниями, а затем с обратными вызовами, которые должны быть достаточно сложными, чтобы дать вам некоторое представление о том, о чем я говорю.
С обещаниями:
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);
}
С обратными вызовами:
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);
}
});
});
});
}
Могут быть некоторые умные способы уменьшить дублирование кода в версии обратных вызовов даже без обещаний, но все, что я могу придумать, сводятся к реализации чего-то очень похожего на обещание.