 All right, just that's everyone hear me all right. How's everyone doing this morning? No good? All right, we're gonna go ahead and get started. I am James Snell. I am head of research at Neoform. Also a Node Core contributor. I've worked on quite a bit of things within Node. Right now, the thing I'm working on is the quick implementation that Trevecom was talking about yesterday. But at Neoform, one of the things that we do is, we have a lot of customers that come to us and ask us, hey, our stuff is slow. Come help us figure out why our code is slow. And over the past couple of years, we've come to ask one very specific question when a customer comes to us and says, hey, our stuff is slow. The first question is, are you using promises? All right, and if they'd say yes, our response right away is, you're using them wrong. Well, you haven't even looked at our code yet. We don't have to. You're using them wrong, right? So what we have found in the overwhelming majority of cases when anybody is using promises, they are, in some way, at some level, using them incorrectly. So that is the inspiration of this talk is basically all the ways that we have seen customers using promises incorrectly and kind of breaking down why it's incorrect, kind of how to avoid those issues. Now, one example, they got called up with a customer, they are having some really poor response times on their API. I decided to do a 30-second benchmark on one path of their code, right? So we're basically just sending the same request over and over and over again against their server, executing a single branch, a single path. Within that 30-second benchmark, they were allocating 30,000 promises. Okay, I'll explain why in just a little bit. But there are a few other examples like that that I will share with you, all right? So promises are powerful. Yes, they're in the language. You really have to understand how they function and what their purpose is, right? When to use them and especially when not to use them, right? Everything that I'm gonna show you is based on what we have seen actual customers do in production code, right? So some of the examples, I've simplified the examples, I'm not gonna say what customers and what companies are doing what, right? So I've completely anonymized it. Some of the examples may look a bit contrived, but they are based on stuff we've actually seen in the real world. But first, I wanna do a puzzle. I've done this a few times. This code prints a message, it's a secret message, right? Without running the code, try to figure out what the message is. You could, you know, without trying to copy down here, you can scan the QR code. I'll also post this on my Twitter feed after this. But, you know, the task is try to figure out what the message is without running it, all right? What this code is doing is using all the various ways you can schedule asynchronous activity in Node. So we have next ticks, we have immediate, we have some promises in there, qmicrotask, right? So this will print a message out. If you figure it out, again, without running the code, let me know, all right? So I'll give you a moment to scan the QR code there. The reason I like to start with this particular example is because it emphasizes the fact that you really need to understand when code is being executed within Node, or within JavaScript in general, all right? So we all know Node is a JavaScript platform, right? Is JavaScript running all of the time in Node, right? No, all right? So let me show you a quick example of this. So if I do a set interval, right? I'm gonna set it for about a second. Actually, let me change this a little bit. We're gonna set it to run every 500 milliseconds and we're gonna have this thing do a for loop and then do a console log. Very, very simple application. So I'm gonna run this. I'm gonna turn on trace events and we're gonna turn V8 and async hooks. This is basically gives some insight into what is happening under the covers on this. And they're gonna run this. So it's just gonna iterate through five times. That's gonna end up creating this file, this Node trace dot one file. Let's pop back over. Chrome tracing is, it's a utility built into Chrome browser allows you to view trace event log generated by Node or by the browser. So let's load this thing up. This file that we just created, this Node underscore trace, might be a little bit hard to read there from the back, but these pink boxes right here are the times when Node and V8 are actually executing JavaScript. The blank spaces between are times when Node JavaScript is being run. That is times when the Node event loop is doing other things. In this case, it's waiting for that timer to fire. So within that time, the event loop is actually spinning. But while the pink boxes are executing, the event loop is not spinning. The event loop is completely blocked. Now, when we say schedule asynchronous activity, what we're talking about is in one of the pink boxes, triggering some action that is not gonna complete until another pink box starts. On this. So basically, like we wanna do a file read, right? File reads in Node happen in a thread pool. So we trigger the file read. It's gonna go off and read the file in a separate thread when that file, the data is available after the event loop turns, right? It'll see, okay, that data is available now. Then it'll start executing JavaScript again, all right? With a promise, you never ever want to create a promise that resolves within the same execution block that it was created, if you're gonna avoid it, all right? When I say you can avoid it, if you're using like promise.resolve, right? That is explicitly a synchronous promise, right? With promises that you don't ever use them where they resolve in the same block. You wanna use it when we need to create it. You're scheduling some asynchronous activity. It's happening off the main event loop. And then it's gonna come back and execute later on, right? That's key, that's number one. And I'll get into a little bit about why that is a little bit later. Okay, so people do weird things with async functions and promises. They pass them to functions that don't expect them. So creating an immediate timer with an async function or setting a recurring timer with an async function. What happens in this case if this throws? What happens to that error? Yeah, I'm gonna get a support email. That's basically what you do. What ends up happening in this case is this error catchable in any way? I mean, you can do a try catch inside this thing, but then your error happening is limited inside that. You can't bubble it out anywhere, right? So with this case, what you end up with is an unhandled rejection. You can't actually catch any errors thrown within that async function outside of that function, right? We see this in all kinds of places. So if we take a look at event emitter, people love passing async functions to handle events. Now, just recently within NodeCore, I'll show an example of this later, we just had a new feature landed called capture rejections that makes event emitter aware of promises. But most cases, if you're talking about event emitter, if you're talking about callback functions, these things are not expecting promises. They are not expecting async functions, and they do not know how to handle them correctly. And what you're gonna end up with are cases where you are either, at the best case, you have an unhandled rejection. At the worst case, you have memory leaks, resource leaks. In this case, we would be leaking a file descriptor, all right, that would never end up getting closed if there's an error somewhere in here. You end up with async in its context, it's not propagated properly down through all of your callbacks or all your promises, right? You can end up with orphaned promise chains, they have, you have no way of handling, right? And all kinds of other kind of nastiness. And we actually get into debates with this, you know, Mateo Collina's gotten on Twitter and said, don't mix callbacks and promises together. And then we actually end up with debates on this, on this stuff's like, no, it's no problem. We can show specific examples here of memory leaks happening, right? I have one in here, let me look at this. Okay, so right here, we open a file, and it's just kind of the local file, we read it, we open the file descriptor, we have some error handling in there, right? Then let's call a function, happened to be something that, the reference is not resolved, right? In this case, that async function is gonna throw with an unhandled rejection and that FS close is never gonna fire. Now, a lot of times we'll have a promise or a process on unhandled rejection to do some logging, right? So maybe you'll see an unhandled rejection or an error in your logs, but the actual file descriptor itself never actually gets closed. We see this all of the time. Now it's possible to get around this, you use try catch in this function, you can use a finally in here to close it, but most of the time developers completely forget to do that, right? This is one of the most common issues that we see. All right, so this is, and these are things that are easily checked. The simple rule on this, don't pass an async function or promise to a callback that does not expect it, period. Just don't do it, all right? Now another weird one that we've been seeing lately that's just bizarre are people mixing new promise and async functions, right? The oddest one of this that I've seen recently was await new promise that was passed an async function. And then to resolve, it was resolve await something. I still don't quite know what they were trying to accomplish, right? It was not what they expected, all right? Let me ask you this. If I have a regular function like this, a new promise, and I throw in here, right? What's gonna happen? Is that gonna get caught or is it gonna be thrown immediately? It's immediately thrown. A lot of folks think anything that's any error that's within a promise is gonna get caught by the catch handler. It is not, right? It is only caught if it happens in a dot then or dot catch, right? In one of the chain. The function that is passed to the promise itself that will throw immediately. When is this function executed? Immediately, right? There is a common misconception particularly among developers that are coming from Java and .NET environments that new promise is equivalent to new thread, right? There is, you know, it's a very common misconception. I had to sit down and prove it for I think it was about 45 minutes. Go through a proof for customer that we were at with the velprincy. They were absolutely convinced that new promise was running in parallel, right? Just kind of magical new threads. There's a huge amount of misconception out there about how this works, right? So this error is gonna get thrown immediately. What happens if I do this? If we pass an async function and we throw that error. Again, where we're gonna end up with is an unhandled rejection, all right? Now, do you think the catch handler is gonna catch it? Let's just console log it, right? Console log the message, right? You think that's gonna handle it? There's a common belief that, yeah, if you pass a promise to this thing, you're worth it in promises, it will not catch it. You get an unhandled rejection. The catch handler cannot see this in any way, but we are seeing this more and more and more. I think we've seen this in, I think, three or four customer projects just over the past couple of months, right? And this is based on a belief that, hey, async functions are great, let's use them everywhere. No, just no at all, all right? So we have that one. Very important, you have to be very careful about where promises are actually being used. They are a very specific tool. They're used only for scheduling asynchronous activity and waiting for the results of that activity, right? But as we'll see as we go along, there's lots of other ways that people are abusing these things, all right? So let's move forward here. All right, so if we're not going to be passing an async function to a callback, if we are not supposed to be mixing these things, how do we actually get by it, all right? The answer is if you're going to be using promises, go all in on promises. If you're going to be using callbacks, just do everything callbacks, right? If you're going to be mixing them, it actually gets very complicated. I'll show you an example of kind of doing it right, and I'll show you how complicated it gets. This, in this case, we're using promises throughout the entire thing. So we're using the promised-fied version of the FS module, right, so we have a promised-fied version of Open that we can await. What this version of Open gives you is a file-handle object that will automatically close if you forget to do it. Now, it'll give you a warning, but it will go ahead and clean up after itself if you forget to do it, to avoid that memory leak, but it'll be noisy when it does it. It'll say, hey, you should be explicitly closing this thing, do that next time, all right? So this is a much safer way of doing this. Error handling is consistent throughout, and we're properly propagating errors in context here, all right? So just as a general rule, do not mix callbacks and promises. Do one or the other, all right? Now, it is possible, like I said, to combine them, but it becomes much more complicated, all right? So in this particular case, I'm creating the promise, I have to resolve reject, I have two callbacks, two nested callbacks that are happening, right? I'm having to propagate the reject through those different levels. So this is, if you've heard of callback hell, this is callback end promise hell, and it just gets even nastier, right? And the problem, the reason this is nasty is that promises and callbacks operate on two completely different abstraction models, right? Yes, both involve scheduling async activity, but how they are resolved, and the execution model are entirely different and incompatible with one another. So you have to kind of munch them together in order to get them to work. Now, utilities like utill.promisify, which will take a callback function and wrap it, it does quite a bit of work under the covers to make sure this is done correctly, right? So make use of utill.promisify. I have a few examples of that in here. This, like you can do, you want a promisified version of set timeout, and you can do, let's say sleep equals utill.promisify set timeout, right? And then from there, sleep actually becomes promisified, right, you can do whatever, right? And that actually, it won't block the event loop, it's just a timer that's happening under the covers, but it's been wrapped in a way that does proper propagation of errors, all right? Event emitter. This was mentioned in Joe Sepe's talk earlier. If you wanna create an event emitter that understands proper error handling with async functions, you can do, let's see, if you pass in the capture rejections. Now, this isn't in a node release yet. So it's not gonna work with the version of node I'm using here, because I'm using a release version. So this will error, it won't work properly if I run the example, but what this will do is it can pass an async function in, if it errors, it will cause a error event to be emitted properly, right? It won't be hidden by the promise chain, okay? So there are ways to do this correctly, it's just complicated and difficult, and you know, Matteo, Colleen and his talk yesterday made the point that you might spend a ton of time in your code doing it correctly, but I can guarantee you there's somebody on your team or another team that you're working with that will just do it wrong, right? So it's very difficult to do this consistently, and we have seen this time and time and time again. We're especially with people not handling errors correctly that it just falls down, okay? So, all right, so let's move on. Let's see, here's the event emitter. All right, so in order to do it correctly without Matteo's fix, by the way, if you're on a version of Node that's not, that doesn't have to capture rejections yet, you can do it, but what you have to do is, within a try-catch and you're erasing function, you catch the error, and then you have to make sure you emit that in a process next tick. The reason we have to do that in a process next tick is because emit is a synchronous function. When it is operating, when you call it in this context from an within an async function, its error handling is bound to that promise, right? So if you throw, it's just gonna be part of the same caught throw, right? And you'll end up with an unhandled rejection. So next tick allows you to escape that, right? Allows you to escape the promise, right? So if you're within that and you absolutely want to get out of the promise error handling, you can always use next tick to do so. It's ugly, it's nasty, but it works, all right? Okay, let's move on a little bit more here. So the other, this one's fun. How many of you have seen long then chains in your code? All right? Dot then, dot then, dot then, dot then. The worst one of these that I've seen was about 15 or 16 of these chained. It's in a module that's actually pretty popularly used. We recommend now if we see it, just don't. Just don't, don't use it. And what we typically find when people have these long dot then chains is that they're being used or basically make the code readable, right? And they're typically end up being used for flow control of synchronous code. So in this particular case, we see, and this one's pretty contrived, but it's, you know, again, see this all the time. Yeah, we have two upper, two lower, reverse. We're basically just isolating functionality into individual functions, and then we're gonna chain them together, you know, with dot then. This is absolutely pointless. All of this code is resolved synchronously, right? It's all gonna be executed synchronously. It's all gonna be blocking the event loop in one turn, right? These promises are gonna get resolved in the micro task queue. That's the part of all this that executes the dot then and dot catch handlers will actually be executed synchronously immediately following the resolution of these promises. There is no asynchronous activity happening here at all. All you're doing is taking a block of code that executes right now in the event loop and moving it, okay, right here in the event loop, but with a whole bunch of promised allocations on top of it, right? Do not do this. Don't ever wrap, use a promise to wrap purely synchronous code. This is probably cardinal sin number one with promises, all right, but we see this all of the time and every single time it's because it makes it more readable, right? It might make it more readable, but it's gonna absolutely kill your performance. We've seen this code like this, we've seen it take code down maybe 60, 70% performance wise just because they're doing this kind of stuff, all right? To make it even worse, people will do things like make these async functions which just causes even more promise allocations. When you await an async function, do you know how many promises are created? Just by, you know, if it's just an empty async function that does nothing else, if you await that function, it creates three promises every time, no matter what else it's doing, right? Every time you await an async function, all right? So you need to be aware of what you're actually allocating here and for what purpose you're actually allocating those for. All right? So for this, you know, it's simple. If you absolutely need that get dated of return of promise, right? Go ahead and resolve the promise synchronously, right? Use promise.resolve, that's what it's there for. That's the intent. Run your code synchronously and save yourself the trouble of all those additional promise allocations, all right? There's no other correct way of doing this, right? Do not wrap purely synchronous code in a promise. In a promise chain of .dense, the only place you should have purely synchronous code is in the final then handler. That is the only place, all right? All right, let's keep going. Loops, oh, these are fun. Passing an async function to a functional loop, like for each or map or filter, all right? The example I told you at the beginning where the people were creating 30,000 promises, this is how they were doing it. They were parsing a one meg JSON file, iterating synchronously over every field in that file, creating a promise for every field, not just a promise, a promise chain for every field, and a majority of those promise chains were resolved synchronously. So JSON parsed the synchronous code, iterating through the synchronous loop, allocating thousands of promises in a synchronously, and then resolving them all synchronously. Now, let's see why this is a bad thing. Let me show you. So we're gonna go ahead and run node again with our trace events, and then this one is, so we're gonna let this run for a bit, and all this code is taking, setting a series of numbers and then inverting the values, right? We've got about a thousand of them in there. All right, so it's gonna create that trace event file again. So how many promises do you think were created during that operation? Let's see, no, let's see. Why didn't that go? All right, hold on a second, yeah, make sure my, all right, so how many were created during that loop? Why is this not coming up with all the information? I think I'm specifying something wrong here. Oh, yeah, no, it's no dotties and cooks. So how many promises do we think? We said five, actually there's about 8,000, and we can see that here. Every single one of these lines is a promise, several promises. So these are all being created in a synchronous loop, right, the iteration was a thousand iterations, right? So we have about 8,000 promises that were created over that synchronous iteration. Now how do we know it was all synchronous? Let's go all the way down here at the bottom, and we see this V8 execute block, right? So this is synchronous code. If we go back to the trace event log, every single one of those promises was allocated synchronously within the exact same block, right? Now in these particular promises, we do have a timer, right? So it is, they are scheduling asynchronous activity, but we're still allocating all those promises all at once. And there's code running in those, synchronous code running in each of those, that is causing this massive event loop block. So during this time, that event loop can't do anything else, right? We have one example of a customer. Their system was receiving a whopping four requests per second. What was it doing? They were parsing very complex GraphQL queries that on the back end, we're talking about 10 or 15 back end services, right? The way the GraphQL implementation in Apollo works, it's very promise heavy. It uses these long promise chains. It goes off and parses the query, walks the query, figures out an execution plan for the query, right? And then kind of goes through and executes the back end queries and then reassembles the data, right? Well, they were receiving a large number of timeout errors as they were running this thing. And that's actually why it was killing their throughput. What was happening is the back end services were receiving their queries and they were returning data back, right? Now the way that the event loop works, when there's data that's been returned, it sits in a queue until the event loop turns and it can say, hey, there's data available, I'm gonna go ahead and fire off the callbacks associated with those, right? But timeout timers fire first, okay? So the event loop was being blocked long enough, it was sending a request, the back end servers were returning the data, but by the time the event loop turned over, it's like, oh, it took too long. So even though the data is sitting there, the system would crash and they were only getting four requests per second through and their solution to that was to throw a thousand more servers at it, okay? That works, I guess. But whenever you're creating promises in a synchronous loop, just don't, or keep your iterations very, very small, right? There's an old rule, don't create a closure, right? Limit the number of allocations you're doing in a loop, same thing here. Every time you create a promise, right? You're allocating something on the heap, right? So we just created 8,000 objects on the heap, right? In a synchronous loop, it's just not something you wanna do. You have to be aware of what's happening when you're creating these promises. All right, so another interesting, this one just qualifies under, I don't have any idea what they're doing, or why, right? Return, await, new promise, async, resolve, resolve first, await. This was based on a real customer example we got like two weeks ago, right? Now what they're doing here is assembling an object over time, right? And these await are actually going off and doing database queries, right? And what they wanna do is if one of them dies, they don't wanna continue, right? So they don't wanna go off and execute all these things in parallel. They do wanna do them one at a time. So they figured, let's spread it out this way. Where they came up with this, return, await, new promise, async? I have no idea. There should be a lint rule against this and it should just reach out and slap you every time you do it, right? So what's the correct way of doing this? There's a couple of ways. Let's take a look at one. Like, well, that's when we're doing it in parallel. All right, so if we really wanted to wait on it, right? And do these things the way that they said, we just do this, right? It's much more readable. It actually makes sense. You're not using strange syntax that should get you slapped and it just works. Now in this case, you're still waiting for 600 milliseconds for everything to complete, right? You're still waiting for one to finish first and if it fails, then you'll stop, right? If you wanted to do it, if you really did wanna do it in parallel, then you could use promise all and goes off and executes these things, all right? Quick comment on promise all and then I'm coming up to, you know, running out of time here. Promise all waits for everything to finish, right? Kinda, it'll short-circuit at the first rejection, right? What happens to the other promises? If one of those rejects, what happens to the other promises? They continue. A lot of folks don't know that, all right? Especially bad when they use promise race, right? Promise race is the first one, you know, the one that finishes first is the winner. What happens if the other one is taking a very long time to finish? It still goes and still blocks your event loop, right? So, using promise race actually doesn't actually buy you very much. There are use cases for it in the browser. On node, it's just gonna kill your performance, all right? So, let me see, yeah, yeah, that's essentially it. I can go back here to the slides. We have some basic rules to follow. Know when your code is being executed, right? Use trace events. If you use the dash dash trace event categories and turn on the V8 category, you will see exactly when JavaScript is running, right? You will know when it's not running, right? You want to make sure that if you are making use of promises that you are spanning those barriers. Your promises should not be resolving within the same event loop, the same V8 execution, right? If they are, go back and figure out what's wrong or don't use a promise there, all right? Don't use unexpected promises. Just don't, right? If a function does not, if you do not know that it is designed to take an async function, do not pass an async function. Yeah, again, avoid mixing promises and callbacks. Use one or the other, all right? Don't create promises in loops. Again, synchronous promises are just useless and do not use long then chains. Just avoid it as much as you possibly can, all right? So that's it, thank you.