È giusto dire che le promesse sono solo zucchero sintattico. Tutto ciò che puoi fare con le promesse che puoi fare con i callback. In effetti, la maggior parte delle implementazioni promettenti offrono modi di conversione tra i due quando vuoi.
Il motivo profondo per cui le promesse sono spesso migliori è che sono più componibili , il che significa all'incirca che combinare più promesse "funziona", mentre la combinazione di più callback spesso non lo fa. Ad esempio, è banale assegnare una promessa a una variabile e collegarvi ulteriori gestori in un secondo momento, o persino collegare un gestore a un ampio gruppo di promesse che vengono eseguite solo dopo che tutte le promesse si risolvono. Sebbene sia possibile emulare queste cose con i callback, ci vuole molto più codice, è molto difficile fare correttamente e il risultato finale è di solito molto meno gestibile.
Uno dei modi più grandi (e più sottili) in cui le promesse ottengono la loro componibilità è la gestione uniforme dei valori di ritorno e le eccezioni non rilevate. Con i callback, il modo in cui viene gestita un'eccezione può dipendere interamente da quale dei molti callback nidificati l'ha lanciata e quale delle funzioni che accettano callback ha un tentativo / catch nella sua implementazione. Con le promesse, sai che un'eccezione che sfugge a una funzione di callback verrà catturata e passata al gestore degli errori fornito con .error()
o .catch()
.
Per l'esempio che hai dato di un singolo callback rispetto a una singola promessa, è vero che non ci sono differenze significative. È quando hai un miliardo di callback contro un milione di promesse che il codice basato sulla promessa tende a sembrare molto più bello.
Ecco un tentativo di alcuni ipotetici codici scritti con promesse e poi con callback che dovrebbero essere abbastanza complessi da darti un'idea di cosa sto parlando.
Con le promesse:
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 richiamate:
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);
}
});
});
});
}
Potrebbero esserci alcuni modi intelligenti per ridurre la duplicazione del codice nella versione di callback anche senza promesse, ma tutti quelli a cui riesco a pensare si riducono all'implementazione di qualcosa di molto promettente.