Videos → What happens when you cancel an HTTP request?
Description
Reactive libraries like RxJS allow us to easily cancel HTTP requests but is that really efficient? What really happens on the back-end? Is it possible to propagate the cancelation from the front-end through our microservices and cancel the database query?
Chapters
- Introduction and Welcome 0:00
- Live Search Implementation Challenges 0:55
- Solving API Overload: Debouncing and RxJS 2:32
- RxJS Explanation and switchMap Operator 4:39
- Cancelling HTTP Requests and API Behavior 6:54
- Speaker Introduction and Marmicode 7:09
- NestJS Overview and Code Example 8:10
- Observables and Cancellability 10:20
- Live Coding Demo: File Search Performance 11:36
- NestJS Interceptors for Request Interruption 16:43
- MongoDB Integration and Performance Optimization 19:56
- How Request Cancellation Works and Key Takeaways 22:01
- Conclusion and Contact Information 24:31
Transcript
These community-maintained transcripts may contain inaccuracies. Please submit any corrections on GitHub.
Introduction and Welcome0:00
A big round of applause to welcome Mr. Younes Jaaidi, developer and extreme programming coach from Marmicode Wishtack. A big round of applause, please.
Live Search Implementation Challenges0:55
Hello everyone. Is that all the welcoming you got? How are you all doing? Okay, so we're going to start with you. Okay, you're doing good? So, I assume everyone is okay. Okay, so thank you for having me here. And I'm going to first start with a question.
Did you ever have to implement a live search form?
Like, you know, you don't want to hit the search button, you want some type ahead or live search. Who did that? Okay, good. Was it easy? Was it fun? Okay, no one. Okay, good. Then it makes my talk useful maybe. So, my clicker isn't working.
Great. And my finger. Okay. So, here it's simple. I just want my search form to go hit my old grumpy API that's going to fetch some data on a random database. And the problem is I have some quick typing, some fast typing users. And it's going to fill my form quite quickly and it's going to send a lot of requests to my API then to my database which might, I said it might, burn up all my servers and back end.
Okay, some of you will say, "I don't care, I'm full serverless." So, it's just going to pop a lot of instances of stuff. And okay, maybe, but maybe you care about money. So, you want to reduce your costs.
Solving API Overload: Debouncing and RxJS2:32
Okay. So, how can we solve this? How can we solve this? Just think one second about what the first thing that comes to your mind about how can we reduce this load on our API and our database? So, the first thing that comes to mind is, does the clicker work? Okay, it works. So, the first thing that comes to mind is, let's put some timer somewhere, like a debounce.
So, how does the debounce work? It means we're going to slow down how the data is sent to the API. For instance, if I have a user who's writing Marmicode.
Okay, so he's typing quite fast. And as you can see, the keyword string is getting longer and longer, but we're not sending it thanks to the debounce. So, we'll see how we implement this, but the debounce will wait for the user to stop touching his keyboard for a few milliseconds and then it's going to send that result, okay? So, instead of sending nine requests to the API, we reduce this to two requests because the user somewhere slowed down in the middle and at the end. Okay? So, how does that work?
Are there any Angular developers here?
Okay, see ya. Alright. Okay, are there any RxJS users? Are you familiar with RxJS? Okay, so I'm going to explain RxJS to your friends around you. And so I don't have much time here. So, how does RxJS work? So, we have a stream of data somewhere like keywords$. The $ is for streams, it's like a naming convention. So, anyway, it's a stream of data, think about it like that. And then we go through some pipes which are like operators that will transform our stream and we get the final results. So, the first operator we'll be using is the debounce,
but first, when we create that observable, it doesn't do anything until we subscribe. And when we subscribe to that data, we're going to call that console.log function that will show the results on our console.
RxJS Explanation and switchMap Operator4:39
So, how does that work? So, first, we're going to add the debounceTime operator, which means it's going to wait for 100 milliseconds before emitting any values, if while the user is typing, it's not going to emit any value. And then we're going to make a query
thanks to the switchMap operator. So, switchMap is a flattening operator, which means it will fetch some asynchronous data somehow with an observable and it's going to produce it will emit the results, okay? I'll put some slides at the end. I'll put some link to some slides about how flattening works in RxJS and which operators you should use. So, here, the cool thing with switchMap is that it will every time we have new keywords, it's going to cancel the previous requests, which is quite cool. It will unsubscribe from the previous observable and subscribe to the new one. That way, it's going to cancel the previous request and we don't have like parallel requests to the server.
Problem is with the debounce, I have this thing which is artificial latency. I mean, come on, we're all paying a lot of money, a lot of time to make like zero latency stuff and to reduce the latency everywhere on our databases and everything. And then we add this 100 milliseconds hardcoded latency to the user input. Isn't that crazy? So, we have to get rid of this. If I get rid of this, it means I'm going to send a request to the API for every keywords the user is writing. And it will every time we have new keywords, we're going to cancel the previous request and send a new one. So, how does that work?
Okay. So, the thing is every time the user is sending new keywords, we're going to cancel the previous request, as you can see here. Problem is, okay, we're canceling the previous request, we're not consuming bandwidth and stuff, but what happens on the back end? Is the code still executed? What happens with the database? Is the query still executed? Are we consuming CPU and RAM and stuff or not?
And what about calling another API somewhere? Are we still waiting for the results or not? What's happening? We still don't know.
Cancelling HTTP Requests and API Behavior6:54
So, that brings us to this thing I want to share with you
is what happens when we cancel an HTTP request? Because it's quite cool to cancel an HTTP request when you don't need the data, but is the API still working there or not?
Speaker Introduction and Marmicode7:09
So, Let me present myself. I'm Younes, from Lyon in France.
And I run a company called Marmicode. We help companies cook like better apps through consulting and extreme programming coaching. I'm very passionate about extreme programming if you want to talk about that. And I'm also a Google Developer Expert for Angular
and web technologies, which basically means I don't know everything about web technologies, so just don't ask me everything. But I can help you with that. And I'm here to really help you and to connect you with the right teams or resources if you need. So if you have any questions about your experience with frameworks, with the web today, what can we do to help you, just let me know. And the other thing I do is extreme programming coaching. So how to pair program, how to be more pragmatic, how to write tests and stuff like that.
NestJS Overview and Code Example8:10
Okay, so without any advertising in between, let's
talk about NestJS. Is anyone familiar with NestJS? Or heard of it? Okay, good. So NestJS is a feature-rich JavaScript framework for
the back-end, Node.js framework. Based on Node.js. So it's based on Express, but not only, you can use other frameworks underneath. And the cool thing, it's very inspired from Angular, so we'll find a lot of similar concepts like decorators, TypeScript and stuff like that. So instead of explaining how Nest works, I'm going to show you some code from Nest. This is a controller that handles the `/files` route.
So the first thing you can see is that we have a decorator that describes this. Okay, so this is handled by `/files`. And then we have the dependency injection, just like in Angular. So I'm going to inject the service called `FileSearch`. And we have a decorator here that says that this method will handle the GET request. And we can inject the query parameters through the `@Query` decorator. Cool. I mean, if you like decorators, it's really cool. And then I'm just going to search my files through
the service. So the idea is, it's an API that will search files through some query. And return the result synchronously, for example, here. Problem is, we're not going to return a synchronous result, of course. We're going to do some asynchronous logic there. And the cool thing is that Nest works also with RxJS and observables. So instead of returning a promise or something, I can return an observable, which has more features than a promise. So I can just run `fileSearch` like before, and I can pipe my operators to transform the data and return that observable that will be transformed to an HTTP response and sent to the client.
Observables and Cancellability10:20
So why would I do that? What's the why would I use an observable? It's not a stream I have here, just having one response. So we need this because there is an interesting feature in observables, it's cancellability. I mean, I can cancel an observable. Like I can subscribe to an observable, ask it for work, and then cancel, interrupt that work because I don't need that data anymore. So how does that work underneath in RxJS?
Well, it works like this, like you create an observable manually like this with `setInterval`, for example, which is not a great idea. It's not how `interval` works in RxJS. And this will produce a value every period.
But the cool thing is that I can keep the timer. What does the timer do? We need the timer when we use `setInterval` to clear the interval, you know, to interrupt it when we don't need that data anymore. So that's where we return our teardown logic. So that's a great concept in observables is that we return the kind of destructor, you know, like the teardown logic that will be executed when we unsubscribe from that observable. So every time I unsubscribe from that data, I'm like, "Oh, I don't need that data anymore." It's going to clear the interval and stop the `setInterval`.
Live Coding Demo: File Search Performance11:36
Okay? So let's try it out. Wait, got to put my coding clothes and let's cook some code.
I hope it's not going to break the mic.
I'm the one who cooks at home. It's not even true.
But okay, let's go.
Didn't work. It's gone. Okay, so where's my code? So here we go. I have the controller here, and it's calling the service. Is the zoom level okay back there?
Great. And so let's search for the function. So here, I'm returning just an observable, a hardcoded observable with the data. So let's create that observable. I created, so the idea is I wanted to create, to prove my point here, I wanted to do something really slow. I want to execute something really slow. So I was like, "What can I query which is very slow?" So I thought, maybe if I have a big file in my file system, and I could search line by line, that could really slow down the app. And it worked quite well. It was not very slow. Then I thought, maybe if I have a lot of files, like searching through a big directory with lots of small files, that could be really slow. Then I was like, where can I find a big directory
with lots of files?
Okay, let's just do it. So, I created this couple of stupid functions just go through node modules. And here I have an observable that just pops out every file path it finds. So, I'm going to pipe, map it, and merge map and read
every lines I find in every path. So, merge map just will mix all the results from all the files. So, for every file, I'm running a stream, I'm trying to get a stream of all the lines in there. Just reading line by line and creating an object for every line which is like super unperformant and I love it. So, don't do this at home. Or if you really don't like your company or something. I don't know. So, I'm filtering the lines. So, every line is an object and I have the content because I have like other fields, I guess. I have the file and the number and I want the content to include my keywords.
Yeah. Here they are. And then I don't want this to run for an hour. So, and I want to group all the results in an array. So, I'm going to use an operator called `bufferTime`. So, it will just wait five seconds at most and group everything in an array. And at the end, I want just one array. I don't want like lots of arrays. Like once I get my first array, I'm going to stop. And last step is here I'm getting all the lines and
what I really need is an objects with items inside.
I don't know why I did that. I could have used just an array. Anyway. Okay. Good. So, this should be really unperformant. So, let's check it out. So, in order to have like some live memory and CPU
usage of my node app, I had to create like small app for this. You will find the source code at the end. So, here, look at this. Look at the network. Every time I write something, the previous request is canceled. But look at my node.js app. It's going crazy. And now I get the results. Or for example, suppose I just start searching a lot
of stuff. So, every key is every time I add a character to my
keywords, it's running a search on the API and it keeps running. And even if I just send the keys and I close the window, my back-end is still working at 100% CPU which is not crazy. So, it's quite a deception because I was expecting that canceling the request on the browser would unsubscribe from my observable on the back-end and stop the processing, but it didn't work. And so I had to dive into NestJS code and I realized that somewhere it's just grabbing the observable and running the `toPromise` method and grabbing the data at the end and returning it with Express.
So, I had to find some way of detecting how the request is interrupted and unsubscribe at that moment from the observable.
NestJS Interceptors for Request Interruption16:43
So, let's go back to our slide. So, we tried it out. It didn't work as you already guessed. So, it didn't work because as I said, Nest doesn't unsubscribe automatically. So, but NestJS has this common thing we have in great frameworks in general is like concepts like interceptors and stuff like that that allow us to intercept every request. So, here what I'm doing is that I'm just writing an interceptor that grabs all that intercepts like every
request coming and then I have a parameter called next, which is the handler that will really execute the code. It will, it's the, it will be mapped to the right router there. And of course, it's returning an observable because we are using observables here. So, this is just forwarding the request, nothing more. So, I want to transform this. So, what I'm going to do is I'm going to grab the request. Well, I'm switching to HTTP because it could be like a websocket or something. And I'm subscribing from events is a function from
RxJS helps us subscribe to an event and unsubscribe at the end from it. So, I'm subscribing to the close event, which is triggered by the socket, you know, from for that HTTP request. It works even with HTTP2. So, and I have now an observable called close that will trigger an event when the connection is shut down. But the cool thing is next.handle returns an observable and there is a great operator in RxJS which is called `takeUntil` that will subscribe to that observable. It will subscribe to the response until something happens. And here the something is close event. So, it will subscribe until the connection is closed and then it will unsubscribe. So, just by adding this interceptor to my app, it should work. So, let's try it again.
Okay, getting all impatient. And see what happens. Where is my code? So, here in my module, I'm just adding providers just
like in Angular. I can add an interceptor and it's exactly the code from my slide. Almost exactly. Well, anyway, it's exactly the same code. And let's see what happens. I didn't change anything in my code. I'm just adding this interceptor.
Okay. Look at this. Okay, looks better. Of course, it's slow because like it's reading node modules, come on. And now if I do this, send keys, you see, it's not like 100% CPU, it's less. And the cool thing is if I start a search and stop, it
immediately stops the processing on the back-end. Or if I start search, then I just close the window, it just stops all the processing on the back-end. So, it's just like if our back-end detected that the browser just shut down or the connection was canceled.
Okay.
MongoDB Integration and Performance Optimization19:56
Now, let's go a little bit further and instead of
using my file search service, going to use dependency injection here and instead of using file search, I'm going to use file search Mongo. It's going to use MongoDB to do this and Mongo was really super fast because I put like all the files in MongoDB and every line and I couldn't slow it down enough. So, the only way I found is like this hacky ugly way where I'm executing like a function in Mongo to check every word in every line, which is not like the best. Don't do this at home. And but it's really super slow and I love it.
And the cool thing, so let us see how it works.
So, without our interceptor that does all the magic, this is what we get. Wait. Stats service.
I'm going to watch MongoDB stats. So, this is not the CPU and memory from my node.js, it's the CPU and memory from MongoDB.
It's really slow. It's not great. Look at this.
100% CPU. And even if I stop, it's just burning everything here. So it's not working. But once I just add my interceptor here, look at this.
Ooh, looks better.
So here it's really searching for every character. Good. Or let's search and then stop. It's really interrupting the work on the database. Closing the window, and it stops immediately.
How Request Cancellation Works and Key Takeaways22:01
So how does this voodoo magic work? It's just because there's no more voodoo magic here. It's just I wrote like this Mongo find function and I create a session in Mongo lib when I do my query. And in the teardown logic, I just go really hardcore
and I kill the session, which is something possible. I don't think it's something you should do in production, but do it and let me know how it works. And because the `session.endSession` didn't cut down,
stop the request violently. It's not very efficient. But the `killSession` works quite well and I don't know. I'd use it, but use it first, please. Help yourself. Okay. So the click works? Good. So the idea is now what we change is that every time we make a request, it's going to cancel the previous one.
And it will propagate the cancellation, which is really cool. It means like if you send a request to an API gateway, the gateway will cancel the request on the next server, the cloud function, and so on. And you can cancel all the work if you have a way to implement a teardown, which is not `killSession`, for example. Anyway, so every request will cancel the rest. So the key takeaways are: think reactive.
It's not easy, but it's really worth it. So we have it's a mind shift. We have to think reactive. It's another way of thinking. We think about streams and stuff. And the best way to learn is to practice. Use observables even for single values. Sometimes don't use observables, use promises because we're like, "Oh, it's just one value and let's just use a promise." Use observables even if you have a single value because you can cancel the request afterwards. You can cancel the processing. And do not ignore the teardown logic. Implement it because sometimes we implement observables. I'm like, "Oh, I'm just going to create an observable. I don't care about the teardown. I'm not going to do the `clearInterval`." So there can be memory leaks there and CPU leaks.
And yes, you can use RxJS everywhere. You can use it in Angular. You can use it without Angular. You can use it on the back-end with NestJS. You can use it without Nest. You can use it everywhere. It's really cool. You can use RxJS in your CLIs, like you write CLI command, and you can use observables there. So these are the key takeaways.
Conclusion and Contact Information24:31
And almost the end, I guess. And last thing is if you need any help, just get in touch with us at Marmicode. We do consulting, extreme programming coaching. We do online consultations to help you like an hour here and there to just unlock issues like, "I run a `killSession` on my MongoDB" and such. And do code reviews, so we can remove the `killSession` stuff from your code. And we do workshops and stuff like that on Angular and extreme programming and JavaScript in general. So you can find me online. You can follow me on Twitter. I'm going to share a blog post on this soon. And you can get in touch through my email here and through the website. You can find the slides on `bit.ly/cancel-http`.
You will also find some slides for the RxJS flattening strategies and stuff like that. And I might add some links. Like for example, if you have questions about where do I learn RxJS and stuff, I can add links there later if you want. ขอบคุณครับ and one last thing before you go crazy, can
I take a picture with you, guys? Yeah! And you do like you pretend like it was really cool talk, like the best ever, and like there is a huge party outside. I'm going there and raise your hands like crazy. Throw out your t-shirts and stuff or all your clothes. Yeah! I don't know if I see it's just all blurry. I'm really bad at selfies. I should train at home. And the guys from Hey, people from the top there! Hey! They have like gold tickets. They're drinking champagne there. Okay. Well, thank you very much. ขอบคุณครับ Thank you, Younes. Thank you. Thank you. Such a pleasure time to be with you. อันนี้เป็นหัวข้อของคุณยูเนสนะค่ะ ท่านไหนที่สนใจอยากจะคุยกับคุณยูเนสต่อ ก็เรียนเชิญได้ที่ห้อง Q&A นะคะ ออกจากเธียเตอร์แล้วไปทางขวา จะมีห้อง Q&A อยู่ด้านข้างทางฝั่งขวานะคะ So if you like to talk to him personally, คุณยูเนส Jaaidi, we have a Q&A room once you go out from this theater, turn right and you will see the Q&A room, okay?
One more thing for me, one more reminder. For anyone of you who haven't choose your special menu for lunch yet, please enter to our browser.
No `www`, just `app.javascriptbangkok.com`.
`app.javascriptbangkok.com` because we have one more session and then we're taking a break for our lunch meal together. So please choose your lunch meal, your lunch venue. And once you choose your lunch meal, please show this
app, this browser to our staff on the seventh floor. You will get the lunch coupon, okay? You need to get the lunch coupon first.