I'm using JavaScript promises extensively in a single-page app I am developing. In certain contexts, I need the "then" method to run synchronously if the promise has already been resolved. For this purpose, I have written a custom promise implementation as a wrapper class, which works fine, but prevents me from using async/await. So I would like to know if there is a way to have both, since it seems to me that async/await is really just syntactic sugar around "then."
The custom promise implementation already implements the PromiseLike TypeScript interface, but apparently async/await always need a native promise. Why?
One possibility I have thought of is to replace the "then" method of a real promise object, instead of building my own wrapper on top. Will this work?
The reason it's important for "then" to be called immediately is that the end of the promise chain is a property of a React component, and the React component displays a loading indicator until the promise is resolved. Without my wrapper, the loading indicator is displayed briefly every time the component updates, which also breaks some user interaction.
Maybe there is a different way to solve this problem. This is my first dive into the world of JavaScript.
I'm using TypeScript and targeting ES6.
but apparently async/await always need a native promise.
No it does not. await works on an object that has a .then method.
Without my wrapper, the loading indicator is displayed briefly every time the component updates, which also breaks some user interaction.
That's not caused by awaiting resolved promises. The .then is executed in a microtask, which means that if the promise really resolved already, the .then will execute directly after the engine executed it's current task, so it gets executed before the browser rerenders.
let promise = Promise.resolve(1);
setTimeout(() => { // Make sure the promise resolved
console.log("sync");
promise.then(() => console.log("then"));
requestAnimationFrame(() => console.log("redraw"));
console.log("sync end");
}, 1000);
You'll see sync, sync end, then, redraw in the console.
Related
I'm given a promise and am returning it at the end of my function for other code to do more actions after me. A kind of middleware, if you will.
return apiCallPromise;
Lets say for some reason I wish to stop those actions from happening and "swallow" a particular promise. For example by doing the following:
if (promiseShouldBeStopped) return new Promise((resolve) => {})
return apiCallPromise;
This correctly returns a promise, however it never resolves. And so the 'promise chain' has been stopped dead in its tracks.
This seems like an anti-pattern to me as it the promise is left pending forever. Are there any bad effects of this that I should be aware of, or is this a fine technique for "filtering" promises?
Attempt to answer my own question
Does this cause memory issues?
Future code doesn't know that this promise has been filtered out, and so fails to do any "catch" or "finally" cleanup code.
In my case this is not relevant, and I want this code to take care of those errors so that the future code doesn't have to deal with them. (this means I can guarantee that onFulfilled gives the api response, and onRejected only gives an error that is actually unexpected)
I am confused with certain parts regarding promises, I have read multiple articles and I have seen multiple videos and I want to ask a few things:
from what I understand currently, when a promise is created is starts running.
Do I need to await on the promise if the value that returned from the promise is not used in the code ?
a scenario for that is: let's say I am processing a task in my system and I want to log the information to mongodb, when I invoke the insert function I get a promise back. the execution beings but I don't care about the result of it
if I am not awaiting and there is an error I wouldn't be able to handle it.
A followup question to the question above:
from what I read whenever I await it actually blocks the execution of the async function, but if it blocks the execution of the function how doesn't it block the rest of the eventloop ?
Basic concepts
The whole point of the event loop is to have many microtasks that do not affect each other (hence by default there is no effect).
To chain microtasks first; there were callbacks, then Promises (then/catch) then the async/await API. The last two can be considered just syntactic sugar atop the callback concept. There are no 'functionalities' added but rather a different syntax to achieve the same stuff in simpler and more elegant ways (Pyhilosophicaly).
The event loop executes all the queued microtasks at each loop and repeats. Unless you have blocking code (and await is not to be considered blocking) your event loop never stalls and hence other tasks are not affected.
You are trying to understand await from the perspective of real blocking code as intended in other languages.
IMHO you first need to deeply understand how callbacks work, then study Promises (as a tool to make callbacks less messy) and then async/await (as a syntax to make Promises pretties). But keep in mind, the underlying system is the same: functions that call functions that get handled functions to be eventually called in future).
Specific questions
When a promise is created is starts running
Yes, but no. A promise does not run, a promise is only a contract you receive by a part of code that will be used to notify you of the outcome. So the promise does not run, is the mean that has been created for you after you requested a task to be executed.
So typically if a promise has been handled to you there is something 'running'. But Promise may be used differently and there may be something 'waiting'.
A promise is not linked to the execution of the task hence it can not start nor stop it.
Do I need to await on the promise if I'm not interested in the outcome
No, you are not required to. But keep in mind that not handling promise exceptions is being deprecated and may result in system failure. You should always handle (or let bubble) exceptions.
There would be a failure if there is an unhandled promise rejection. In synchronous code this is equivalent to an uncaught thrown error. Until now(-ish) uncaught promise rejections were tolerated but there isn't a really good reason for that. Node is moving to treat them the same as any other error that bubbles to the top.
VLAZ
You are considering promises only with async/await but the underlying Promise api is .then() and .catch(). Using this API you can use promises in a 'fire-and-forget' fashion:
async function Do() {
await before();
asyncDbCall().catch(err => console.error(err))
await after();
}
In this example you are not waiting for asyncDbCall() but still .catch(err => console.error(err)) will result in the error being logged (some time in the future, probably even after Do() has completed).
Or you can branch off the execution to other async executions, take this complex example:
async function Do() {
await before();
// This will execute AFTER before() & only if before() succeeded
asyncDbCall()
.then(async value => {
// This will execute after `asyncDbCall()` and only if it succeeded
await something(value);
// We reach here after `something()` and only if succeeded
})
.catch(err => {
// This will execute if `asyncDbCall()` fails of IF ANYTHING
// within `async value => {}` fails
console.error(err);
})
// This will execute AFTER before() and only if before() succeeded and
// asyncDbCall() call (but not Promise) succeeded
await after();
}
Await it actually blocks the execution of the async function
Await stops the async function (hence also anything that is awaiting for the function) but does not affect anyway the event loop.
from what I understand currently, when a promise is created is starts running.
It is not. It has its internal state set to pending. Promise's constructor takes a callback as an argument, and in turn provides it with resolve and reject callbacks.
What it also does is that it allows to provide a number of actions that
happen when it's state changes to resolved or rejected. Outside of async/await, you might know them as .then and .catch instance methods of Promise class. Once the state is changed, they will be executed.
Do I need to await on the promise if the value that returned from the promise is not used in the code?
No, that is entirely up to you.
a scenario for that is: let's say I am processing a task in my system and I want to log the information to mongodb, when I invoke the insert function I get a promise back. the execution beings but I don't care about the result of it
if I am not awaiting and there is an error I wouldn't be able to handle it.
You can still use .catch to handle the error without awaiting for the Promise to finish
A followup question to the question above:
from what I read whenever I await it actually blocks the execution of the async function, but if it blocks the execution of the function how doesn't it block the rest of the eventloop?
Promises have nothing to do with the event loop.
You can read more about the EventLoop here.
If we know that a Promise is definitely resolved, how can we access the value and if we can't, why not?
let a = Promise.resolve(123);
console.log(a.value); // ???
The following does not work- it prints "First, Last, 123"
console.log("First");
Promise.resolve(123).then(console.log);
console.log("Last");
I'm asking how to get the value of an already resolved Promise synchronously and if that's not possible, why not?
No, it is not possible to do this. This is by design.
The Promise A+ specification is meant to be used as a simple, consistent way to deal with asynchronous operations. One of the constraints is that passing a new callback on to then() on an already resolved promise will always execute on a later tick in the event loop, so things are consistent.
Adding a secondary way to inspect promise results would not have been impossible. It would probably have been quite easy to do so, but there's at least 2 problems with adding this to the specification:
If you're looking to build a specification, you want it to be as simple as possible. The specification itself actually only defines the then() function.
By adding this feature, you're opening the door to hordes of developers getting even more confused about something that's already hard to grok.
Promises and asynchronous operations are hard for people to understand. I see questions here daily about promises and not 'getting' it. If non-async way to access promise results would be added to the default promise, I'd imagine that this would be an even larger amount. It's good to try and enforce 'the right way' of doing things.
However, this decision is not simply made for you. You're not stuck there. You can very easily build your own version of a promise that has this feature, and still works with existing promises. As long as your object has a then() function that behaves according to Promises/A+ you can do with the rest of the object whatever you want.
Promises are always asynchronous in JS.
If you're confident that promise is going to resolve then you can access it with .then method.
a.then(function(value) {
console.log("First");
console.log(value);
console.log("Last");
// expected output: 123
});
Variable a will lookalike below if it is console.log
Promise {<resolved>: 123}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: 123
For error handling, you can error block as mentioned in MDN docs.
Promise.resolve(123).then(function(value) {
console.log(value);
}
Take a look on this
I want to be able to reject and stop the rest of the promise from running if an external event occurs. This is the example usecase:
new Promise((resolve,reject)=>{
websocket.onerror=reject;
//do processing here
websocket.onerror=undefined;
resolve(...);
});
This has the intended effect of rejecting the promise if an error occurs while the promise is running. However, the problem is that the rest of the promise will continue running even if a rejection occurs.
Using async/await the above is not possible at all.
I think one way would be to have every single line of code in a separate promise, and cancelling the chain on an event, but that would be a pain.
However, the problem is that the rest of the promise will continue running even if a rejection occurs.
This is a basic misunderstanding (and you're not alone in it!). There is no "rest of the promise." A promise is just a means of observing the completion of an asynchronous process. It's the process, not the promise, that's continuing.
How you cancel that process (and whether you can) depends entirely on what that process is and whether it provides a means of cancelling it, which is unrelated to promises themselves. You seem to be using a web socket, so you'd need to use the web socket API to cancel whatever you have it doing (or to send a message to the other end to tell it to stop what it's doing).
For instance: Suppose the promise were waiting for a timer (setTimeout) to fire, and would resolve when it did. Then some other thing happens and you no longer want that. Rejecting the promise just rejects the promise, it doesn't have any effect on the timer. You'd need to cancel the timer, too (clearTimeout).
Re your edit:
I think one way would be to have every single line of code in a separate promise, and cancelling the chain on an event, but that would be a pain.
No, the code in the promise executor (the function you pass new Promise) is run synchronously. Again, there's no "rest of the code" to cancel. There may well be asynchronous callbacks in there, or a process the code starts you need to send a cancellation too, but that's not going to require putting every line in its own promise, not at all.
Understanding the answer to this question may help you:
When you create the promise, it is executed synchronously. So this line is executed:
websocket.onerror=reject;
At this point, your code begins to process. If there is an event of websocket error, it is put in the js event loop, but your promise is still executing synchronously. Then you unhook the websocket's error handler, and resolve. The promise will resolve even if there was an error. At this point the call for resolve is also added to js' event loop. Then the next js tick executes, and maybe reject is called, maybe not, but it doesn't matter at this point because you resolved synchronously, and promises never change their minds once they've resolved or rejected.
So the key takeaway here is that creating a promise executes it synchronously. It's not a thread that you can cancel while waiting for other events. The promise' resolution or rejection is executed asynchronously, but that doesn't help you at all.
To cancel a promise, you'll need some pretty basic stuff such as
if(someOtherVariableThatDeterminesCancelation){
reject()
}
I'm running an Angular app and when testing on protractor a click(), I don't know when should I resolve the promise with a then().
I found this on Protractor API:
A promise that will be resolved when the click command has completed.
So, should I use click().then() in every click?
So, should I use click().then() in every click?
Definitely not.
It's not needed because Protractor/WebDriverJS has this mechanism called "Control Flow" which is basically a queue of promises that need to be resolved:
WebDriverJS maintains a queue of pending promises, called the control
flow, to keep execution organized.
and Protractor waits for Angular naturally and out-of-the-box:
You no longer need to add waits and sleeps to your test. Protractor
can automatically execute the next step in your test the moment the
webpage finishes pending tasks, so you don’t have to worry about
waiting for your test and webpage to sync.
Which leads to a quite straight-forward testing code:
var elementToBePresent = element(by.css(".anotherelementclass")).isPresent();
expect(elementToBePresent.isPresent()).toBe(false);
element(by.css("#mybutton")).click();
expect(elementToBePresent.isPresent()).toBe(true);
Sometimes though, if you experience synchronization/timing issues, or your app under test is non-Angular, you may solve it by resolving the click() explicitly with then() and continue inside the click callback:
expect(elementToBePresent.isPresent()).toBe(false);
element(by.css("#mybutton")).click().then(function () {
expect(elementToBePresent.isPresent()).toBe(true);
});
There are also Explicit Waits to the rescue in these cases, but it's not relevant here.
Yes, you should.
Maybe right now it's not necessary, but maybe in next versions it is.
So, if click return a promise, you should use it.
http://www.protractortest.org/#/api?view=webdriver.WebElement.prototype.click