Można śmiało powiedzieć, że obietnice to tylko cukier syntaktyczny. Wszystko, co możesz zrobić z obietnicami, które możesz zrobić dzięki callbackom. W rzeczywistości większość obiecujących implementacji zapewnia sposoby konwertowania między nimi w dowolnym momencie.
Głównym powodem, dla którego obietnice są często lepsze, jest to, że łatwiej je skomponować , co z grubsza oznacza, że łączenie wielu obietnic „po prostu działa”, podczas gdy łączenie wielu połączeń zwrotnych często nie. Na przykład banalne jest przypisanie obietnicy do zmiennej i dołączenie do niej dodatkowych procedur obsługi, a nawet dołączenie procedury obsługi do dużej grupy obietnic, która zostanie wykonana dopiero po spełnieniu wszystkich obietnic. Chociaż można w pewien sposób emulować te rzeczy za pomocą wywołań zwrotnych, wymaga to znacznie więcej kodu, jest bardzo trudne do prawidłowego wykonania, a wynik końcowy jest zwykle o wiele trudniejszy do utrzymania.
Jednym z największych (i najsubtelniejszych) sposobów, w jakie obietnice zyskują swoją zdolność do kompozytu, jest jednolita obsługa wartości zwracanych i nieprzechwycone wyjątki. W przypadku wywołań zwrotnych sposób obsługi wyjątku może zależeć całkowicie od tego, która z wielu zagnieżdżonych wywołań zwrotnych go wyrzuciła, i która funkcja odbierająca wywołanie zwrotne ma próbę / catch w swojej implementacji. Dzięki obietnicom wiesz, że wyjątek, który wymyka się jednej funkcji zwrotnej, zostanie przechwycony i przekazany do procedury obsługi błędów dostarczonej z .error()
lub .catch()
.
W podanym przykładzie pojedynczego wywołania zwrotnego w porównaniu z jedną obietnicą, to prawda, że nie ma znaczącej różnicy. To kiedy masz zillion callback w porównaniu do zillion obietnic, kod oparty na obietnicach wygląda na znacznie ładniejszy.
Oto próba jakiegoś hipotetycznego kodu napisanego z obietnicami, a następnie z wywołaniami zwrotnymi, które powinny być na tyle skomplikowane, aby dać ci pojęcie o czym mówię.
Z obietnicami:
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);
}
Z oddzwanianiem:
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);
}
});
});
});
}
Mogą istnieć sprytne sposoby ograniczenia duplikacji kodu w wersji zwrotnej nawet bez obietnic, ale wszystkie, o których myślę, sprowadzają się do implementacji czegoś bardzo obiecującego.