Closed AnupamKhosla closed 2 years ago
Promise is a language-specified feature, so you generally have more well-behaving semantics than a random API from a random library. It's not to say callbacks can't have these guarantees—it's that they don't necessarily do. Promises set stronger developer contracts than many callback-based APIs at the time (which was more than 10 years ago).
Callbacks added with
then()
will never be invoked before the completion of the current run of the JavaScript event loop.
Consider this:
function doSomething(cb) {
if (Math.random() > 0.5) {
cb();
} else {
setTimeout(() => cb(), 1000);
}
}
This function is a notorious example of the Zalgo, because the callback may be synchronous, and may be asynchronous—you may never know. So the following is very hard to reason about:
let x = 1;
doSomething(() => {
x = 2;
});
console.log(x); // 1 or 2? We may never know...
This doesn't happen with promises, since the call order is always deterministic, and what should be async stays async.
These callbacks will be invoked even if they were added after the success or failure of the asynchronous operation that the promise represents.
Again, not to say callbacks can't have this guarantee—just that they don't need to.
Multiple callbacks may be added by calling
then()
several times. They will be invoked one after another, in the order in which they were inserted.
Callback nesting means the first handler has to know the existence of the second handler. This is not a problem if the entire code is written by you; but in case you are passing callbacks defined elsewhere, it's more convenient if you have a fluent-API-like interface:
doSomething().then(handleA).then(handleB).then(handleC);
doSomething((a) => {
handleA(a, (b) => {
handleB(b, (c) => {
handleC(c);
});
});
});
Not to mention the ugly callback hell this creates and how intractable error handling would be. Hence why this paragraph is directly followed by chaining.
@Josh-Cena Great example with Math.random
to show the importance of how Promise
gives the certainty for the order of execution of things. But I have questions regarding guarantee2, guarantee3 and your last example.
Firstly you say, "not to say callbacks can't have this guarantee—just that they don't need to" -- My point is callbacks always have this guarantee. MDN should choose better wording for Unlike old-fashioned... I think the old-fashioned callback does come with this guarantee. E.g. guarantee2:
adding a callback after the success/failure of an async operation:
let fooComplete = false
function foo(cb) {
if( cb != undefined && fooComplete != false) {
cb();
}
//do task1 async work and when asyn work succeeds switch flag
document.addEventListener("MyAsyncFinish", function(){
fooComplete = true;
if(cb != undefined) {
cb();
}
});
}
foo(); //cb will be called later
// async task succeeds say in 2secs
//later in the code after some timeout says5 secs
//...
foo(cb); //cb is guaranteed to execute after first task
There is no way the callback won't execute after the success of the async work.
Then you mention ...but in case you are passing callbacks defined elsewhere, what's wrong with that
foo(cb_from_lib) {
//bla bla
cb_from_lib();
}
Do the then
functions provide more readable and managable syntax/code, yes!. But this is not what MDN is saying, they are saying the old-fashioned callbacks don't have this guarantee3. I don't see how.
doSomething((a) => {
handleA(a, (b) => {
handleB(b, (c) => {
handleC(c);
});
});
});
handleA --> handleB --> handleC
the order is guaranteed to be followed, contrary to MDN's claim.
My point is callbacks always have this guarantee. MDN should choose better wording for Unlike old-fashioned... I think the old-fashioned callback does come with this guarantee.
Again, my point is not that you can have well-behaving callback-based APIs (which all your examples here are). My point is that it's entirely possible for you to design messed-up callback APIs. This means users have to do the additional verification work that the callback always behaves sanely. For example, let me give you a bad API design:
let done = false;
function doSomething(cb) {
if (!done) {
doSomethingImpl((val) => {
done = true;
cb(val);
});
} else {
console.log("Why do you want to do it again?");
}
}
doSomething((val) => {
doSomething(() => {});
});
(This is not an entirely adequate example—in typical callbacks, you don't have the ability to attach extra callbacks to a running task at all; another call to doSomething
should initiate a new task. But let's assume that it does happen.)
Would someone in their right mind do this? Maybe not. But can you be certain that it never happens? You cannot, without (a) trusting the library author (b) reading their source (c) reading their documentation (and assuming they bother to document every aspect of the behavior).
In promises, this can never happen. As long as the library returns you a promise instance, you know with certainty what happens when you register a callback. This is because the job of maintaining the callback queue and deciding when to call them is delegated to the promise implementation, rather than manually implemented by the author.
Instead of telling you how bad callback APIs are, this section is trying to tell you how good promise is. The reason is, the developer of the API does not care when the callbacks are called—all the plumbing details of when, whether, and how callbacks are called are maintained by the promise itself, and both the user and developer automatically gets strong semantic guarantees.
I'll see what I can do to rewrite this page—the page structure doesn't look that clean in general.
MDN URL
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
What specific section or headline is this issue about?
Guarantees
What information was incorrect, unhelpful, or incomplete?
I am asking more of a question, the documentation mentions three Guarantees I have seen the first one in action, e.g. this SO post explains that, but the second and third gaurantee don't make sense,
Well how does a callback doesn't have this guarantee?
The article itself gives examples of multiple callbacks via nesting, which execute one after the other.
What did you expect to see?
I expect the second and third guarantee to be valid for old-fashioned callbacks as well.
Do you have any supporting links, references, or citations?
No.
Do you have anything more you want to share?
No.
MDN metadata
Page report details
* Folder: `en-us/web/javascript/guide/using_promises` * MDN URL: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises * GitHub URL: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/guide/using_promises/index.md * Last commit: https://github.com/mdn/content/commit/eada29e0774d505becb3a725001d372f0dbdc73d * Document last modified: 2022-09-13T08:52:08.000Z