È 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 forniscono modi di convertire tra i due ogni volta che vuoi.
La ragione profonda per cui le promesse sono spesso migliori è che sono più componibili , il che significa che combinare più promesse "funziona", mentre la combinazione di più callback spesso non lo è. Ad esempio, è banale assegnare una promessa a una variabile e allegare ulteriori gestori ad esso in un secondo momento, o persino associare un gestore a un grande gruppo di promesse che viene eseguito solo dopo che tutte le promesse si risolvono. Sebbene sia possibile emulare queste cose con i callback, richiede molto più codice, è molto difficile da eseguire correttamente e il risultato finale è solitamente molto meno gestibile.
Uno dei modi più grandi (e sottilissimi) in cui ottenere la loro componibilità è la gestione uniforme dei valori di ritorno e delle eccezioni non rilevate. Con i callback, il modo in cui un'eccezione viene gestita può dipendere interamente da quale dei molti callback nidificati lo ha lanciato, e quale delle funzioni che accettano i callback ha un try / catch nella sua implementazione. Con le promesse, conosci che un'eccezione che sfugge a una funzione di callback verrà catturata e passata al gestore di errori fornito con .error()
o .catch()
.
Per l'esempio che hai fornito di un singolo callback rispetto a una singola promessa, è vero che non c'è alcuna differenza significativa. È quando hai una zillion di callback contro una zillion promette che il codice basato sulla promessa tende ad apparire molto più bello.
Ecco un tentativo di codice ipotetico scritto con promesse e poi con callback che dovrebbero essere abbastanza complessi da darti un'idea di cosa sto parlando.
Con 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 callback:
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 callbacks anche senza promesse, ma tutti quelli che posso pensare si riducono ad implementare qualcosa di molto promettente.