Справедливо сказати, що обіцянки - це лише синтаксичний цукор. Все, що можна зробити з обіцянками, що можна робити з зворотними дзвінками. Насправді, більшість обіцяних реалізацій забезпечують способи перетворення між ними, коли вам потрібно.
Найглибшою причиною, чому обіцянки часто є кращими, є те, що вони більш композиційні , а це означає, що поєднання декількох обіцянок "просто працює", а комбінування декількох зворотних викликів часто не дає змоги. Наприклад, тривіально призначити обіцянку змінній і приєднати до неї додаткові обробники або навіть приєднати обробник до великої групи обіцянок, яка виконується лише після того, як всі обіцянки будуть вирішені. Хоча ви можете впорядкувати ці речі за допомогою зворотних викликів, це потребує набагато більше коду, це дуже важко зробити правильно, а кінцевий результат, як правило, набагато менш досяжний.
Один з найбільших (і найтонших) способів обіцянок отримати їхню комбінованість - це рівномірне поводження зі зворотними значеннями та неприховані винятки. Що стосується зворотних викликів, то, як обробляється виняток, може повністю залежати від того, хто з багатьох вкладених зворотних викликів викинув його, і яка з функцій, що приймають зворотні виклики, має спробувати / уловлювати в своїй реалізації. З обіцянками ви знаєте, що виняток, який уникає однієї функції зворотного виклику, буде зафіксований та переданий до обробника помилок, який ви надали .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);
}
});
});
});
}
Можливо, є кілька розумних способів зменшити дублювання коду у версії зворотних викликів навіть без обіцянок, але все те, що я можу подумати, зводиться до реалізації чогось дуже обіцяючого.