Go in PARALLEL! Use Async and Await like a Pro in Xano!

Ray Deck shows how to use Xano's async functions to run multiple operations in parallel. Launch several async calls, collect their promise IDs, then use await to wait for all results at once. Best for I/O-heavy work like AI calls, not compute-heavy tasks.
Chapters
XAML recently introduced asynchronous functions and they are super useful. But it can be tricky to figure out how you're going to work with them, especially since there's a lot of functionality that's like, what does this stuff really mean? I'm going to take you through the TL;DR of it, of how to get some good use out of asynchronous functions, how to start thinking asynchronously and thinking in terms of promises. Which are a core idea in these asynchronous functions. Let's talk about how to get into this here. We're gonna create a function here and I'm gonna call it asyncTest, which is gonna be the function we can run from inside an asynchronous context. As we might imagine, we're gonna have a function that's gonna contain a message that's gonna get stored here, and we're going to have an integer of, let's see, delay in seconds. Because one of the ideas here is you wanna have something that can run relatively quickly,, but that is going to, um, but, but then can kick things out and then have them come back. But the more important thing, and even I think more exciting way to be making use of asynchronous functions, isn't just a fire and forget. It's going to be having things run in parallel, right? So you can say, I want to have this and this and this and this all fire at the same time. This can be especially important, especially when working with things like, you know, AI requests, which you know are going to take a little while, and you know they're kind of independent of each other. And you can have all of your agents run and then come back and then deal with their answers at the end and collect those answers back, and we'll talk about how we do that with await. But let's take a super simple example to start with. And we're gonna have a message, we're gonna have a delay in seconds, and so what we're going to do is introduce a sleep, right? So we're just gonna introduce an arbitrary pause, and we're gonna just use the delay in seconds to see how long it's supposed to sleep. Then we're going to say, let's add another function. We're going to, you know, add to event queue, Let's see, add event log, add event to log, there it is. And the, you know, the, and we're going to say this is coming from async test, and the message is going to be my message that came in, and have nothing else particularly interesting. This is an event log like I talked about creating in the, in another video in the series, and it's also available through the state change extension. For Xano where I can import functions that have add event to log available to me. But right now I already have it inside my extension because I am a state change member, I don't really use this, and I can just save this here. Now the idea here is it's going to sleep for a certain number of seconds, it's going to add the event to log saying this is the thing I just wrote, and then it's going to return back just whatever the initial message was, right? So if I just do a run and debug of it, I could say my message is "Hello there," and I can say give me a delay of seconds of 2, and thing takes about 2 seconds to run. If I were to, you know, open up my, you know, database over here, I could go find my, you know, log table. I think it's called event event log. There it is. And if I were just to sort it by ID descending, I can find my most recent stuff first. And here I can see the hello there, right? So this sort of shows how I can add an item to the event log and it just shows up in my event log and I know pretty much what happened. Okay, that's great. If I were to say I want to say hello there too, and I want to say that make this wait for 5 seconds, you know, notice that the previous one took about 2.01 seconds. This one will take about, you know, 5.01 seconds or something because it's pretty fast. The idea is I'm just introducing a little bit of delay. This is to simulate what you might do with like an AI call, right? So I can instead of sleeping here, I could have— I'm gonna go call up an AI endpoint or whatever it might be. And here you can see this thing took about 5 seconds, right? So now we've got this. I'm just going to do a quick publish on it so it's easy for me to access later. And I'm now going to make a new function, which is going to— in fact, we'll make an endpoint because this is often a use case for endpoints. And we will say, let's just go into the API group. That is test. I'm going to add an endpoint. And I'm going to say it's a custom endpoint, because I love making custom endpoints. And I'm going to call this async test, right? And now I have my async test. And the idea is going to be that I can run these async functions, and then have them fire off. And I won't have to wait that long for this particular endpoint to go. So I can just call it async. AsyncTest, right? And I get my asyncTest function, which is, you can see I sort of thought about this kind of thing before. And I'm gonna have my message of hello first function, and my delay in seconds is going to be 2. Save. And then, you know, I can, I can instead of, you see how it's set right now to be synchronous, I can click where it says synchronous here, and I can switch it from being synchronous to being async. Now, What synchronous versus async means basically, am I going to wait for it? Let's call it async for a second. We'll say save. We'll see that even though I told it that I want it to run for, let's make something more extreme like 5 seconds. If I do a quick run and debug on it, run, you see it takes almost nothing at all. In fact, also notice that it comes back with something. This is interesting. See, when the return value of an async function is going to be something that looks like a UUID, Right? It's actually the identifier for what we call the promise. Which is what the function is going— it basically is a placeholder, like a little storage key. So that when the function is done, you are able to find out, get the answer from it. And this is kind of cool, because now what we can do is we can say, let's add a function here. And we're going to— you know, say await. And in fact, you know, let me, let me follow my own rule here and say that you're gonna do this by going down to utility functions. And you're gonna find that you can do async function await. Now the neat thing about the async function await is it takes an array of UUIDs. So we're just gonna push onto that array the value that came back from func1, which is the UUID thing. And we're gonna update this. Note that it's only willing to wait for up to 10 seconds right now. We— you can adjust this, but like this is probably something you're going to want to move up northward because the whole reason you're doing this is going to take a little while. And we can say save. And now we can say, uh, instead we're going to tell it to tell me about X1. Okay, now when I run it again, it waits for 5 seconds and then gives me this, which is a key-value pair of the object that was waiting for and the function that came back and, and the result, uh, that came back. And of course, we remember the async test, it returns back what we passed in as the message. And in the course of all this, it's been doing this business of, you know, putting in first function to the, um, into the event log. So the, the This by itself doesn't seem super exciting because all we've done is sort of taken an asynchronous function and turned it synchronous, right? And the, but the, where things get really interesting is going to be where we, you know, just say let's clone this. And what if we had two calls? And what if instead of saying this, we said second function, and made this delay in seconds 2? Now this is significant because running it for only 2 seconds versus this one being for 5, obviously this one's gonna come back first. So the idea behind await is it waits for everything that you push in to come back. So if I give it the second one, in fact this one's still called func1, which is not gonna be that helpful, so let's delete that here. Let's go into this thing that's called func1, da da da da, and we'll call it output, it's gonna be func2, okay? We'll skip that. And we're gonna go into this guy, And we're gonna say this guy here, we're gonna push func2 in addition to func1. Now, what's gonna be neat here is that this thing's gonna wait for the full 5 seconds. Because this one's gonna take 5 seconds to finish. And even though this one takes 2 seconds to finish, it's only gonna come back when everybody is done. So I can go run again, right? And now we have these two functions, which together would have taken 7 seconds, but now only take 1. So I'm gonna go clone this guy again. I'm gonna go rename it, and we're gonna call it, and we'll call it, you know, third function. And we'll give it the output name of func3. And we'll further give it the delay of 7 seconds. Okay? So the idea here now is I have something that previously would have taken me 7 5 2 seconds, which is 14 seconds, right? But now it's only gonna take as long as the slowest one of these 3. And I go down to the async function await, I can say, you know what, we're just going to push onto that, you know, the func3 as well. And this is a pattern you're going to find yourself repeatedly using when you are building these. Like, if they come back with an ID and you're just going to go push that ID onto your await. So we'll say save. And you can identify which key goes with which input, which can also be a pretty handy thing to do. So let me sort of build a dictionary of it, right? So let's say we're going to have, and we'll call it, you know, promises, and we'll make it an object. Okay, and we're gonna say, save that. And the— and I'm gonna move this up here. See, what becomes interesting about this now is I can add another function where I'm just going to do an update variable. And I'm going to update this particular— I'm gonna update promises .first function to whatever came back from— well, actually, this guy's a little bit too tall, too high in the stack, isn't it? So I need to just remove this down, and then I'll go back in, and I'm gonna say update promise.first function to be whatever func1 is, right? That way the promise gets stored in there. And now I can similarly repeat that, and I can say, all right, well, let's clone this, and I'm gonna update you know, dot second function. Actually, this is a third function because it's func3. Third function, right? And this is going to be func3. I can say save. And then I'm going to do this again. I'm going to clone it and call this func 2. And I'm going to say this from promises second function. Now the reason why these things are going to be really, really useful to us is that we are collecting all of our results into x1. But what we might want to do is figure out, well, okay, all these results came back, but, but which one were they supposed to be? I mean, like This thing just has a random key plus second function. How do I know which request it went with and which inputs it was really supposed to go with? And that's the reason why we've been collecting this list of promises, is to find out what they are, and also to get the data that goes along with them, to find out what their original key was, right? So now what I can do is I can just, I'm gonna iterate over them. And so I'm gonna say, let's do a forEach, Over the promises, right? And we'll call that promises as a promise. In fact, it's not over promises. It's gonna be promises, because promises is an object, so we actually need to get the keys of that, right? And that's gonna get us first function, second function, third function. Promise key. Okay, and we'll say save. And we'll— and now because we're inside this, we're going to create a variable and we're going to get, um, promise ID, uh, which is going to be equal to, um, promises.get based on the promise key. Right, so it's gonna be whatever was in the first function, whatever was in the second function, whatever is in the third function, and then that should return the actual key, the UUID-looking thing, because that's what we're collecting as we're doing these update variables all the way going down. Let's say that. Now what's cool about that is because we now know the promise key and we know the promise value, which is, and then the next thing we can get from here is going to be the promise, is then connect that. Right, with my list that I have inside my async function, right, which is what, what it came back with, an object of keys that go with those values. So I'll find out what the output was of each of those. And the— and so what I'm going to do is I'm going to do a, uh, let's, uh, let's create a variable, and the variable name is going to be promiseResult, and the value is going to be equal to x1, because that's my array of promises, and I'm going to do a getter on that of whatever the promise ID was. Update and save that. Now I know what the result was. So for example, from the promise.first function, the output really should be first space function, right? And that will, and that puts me in a position to like collect the outputs of these in a way that actually connects to what my original intents were. Like I know this is supposed to be for first function. So now what I can do, this is, this is gonna be the fun part. I can take that result. And I can say, let's, let's, you know, update a variable, and it's going to be equal to promises. And we're going to do is going to make it equal to promises. And we're going to set promises.promiseKey to that result, right, we're going to remove that little ID that we had sitting in the middle. Now, the idea here is that when we kicked it off, we got these promise IDs, but the promise IDs aren't that helpful, right? What's gonna be helpful is gonna be the result of the promise, and this then puts it all back together, right? We're gonna have to know which thing we asked for goes with which particular answer. And now we can say, let's tell about promises. Save and let's run again. So let's remember, it's gonna take 7 seconds or plus because that's the slowest one, and then it's going to stitch the answers all back together so that the first function, second function, third function all come back with the correct answers. It is interesting that it came back with not knowing what first function was makes me think that I did not do that quite correctly here. So promises.first function equals func1, func2, func3. And the— and over here, promise ID, promise result, promise promises ID, promise result is going to be x1.get. In fact, let's just find— let's, uh, let's— in fact, let me add a response here. I'm going to say, all right, let's find out what x1 is. And I'll say save and save and run again. Okay, so it only ran those second two. Why'd it only run those second two? func3, func2— ah, well, you can see the problem here, right? It ran func3 twice. So let's have it run func1 instead. So func1, and we say update that, right? So now it's running func1, func2, and func3, which is, I think, more what we're going for here. And then we can run again, and it will actually run func1, func2, and func3. Cool. Now this also demonstrates something. I'm leaving in the, you know, Ray trying to figure out what the heck's going on here, because async functions are confusing. They definitely constitute, you know, one of those hardest 5% type things that I keep on talking about. Asynchronicity means you can start using a lot more resource really fast. And of course your XanoBox is limited in its scope. And it talks about using these shared resources, but that means it's got shared memory going on. And that is, that's all sitting inside this box. And the more of these async functions you run, it's just like running more API endpoints all at the same time. And that might not really give it a great time. That's the reason why when you run async functions like this, you don't want to be running lots of compute-intensive functions. You want to be running functions that you're waiting for. And this is where we usually talk about I/O block versus compute block. Compute block means we're waiting for the, you know, the CPU to do math. And like if you're doing video games and stuff like that, lots of compute block, right? Lots of animations, lots of rendering and all that good stuff. But usually what we're waiting for in these situations is somebody else to get back to us. And when we do that, that's where the async functions are really going to pay the bills. So we have— so in this case, you know, all I was doing was sleep. But if I were to be making a call over to a Flowwise API or making a call to, you know, Cloud or call to you know, whatever. And I keep on mentioning AI stuff because it really is a very common use case I run into, that people want to be using these slower AI systems, and particularly using them in multiples because we start to use like, you know, multi-agent type approaches in order to get better performance. And getting mastery of this is really worthwhile. You will find that you can do a lot when you are able to kick things off and run them in a reasonable way. You're also going to find that running a few of them in a reasonable way is going to do a lot more for you than running tons and tons and tons of them in a reasonable way. That even this, this is a little bit like the post-process move of you're kicking things off kind of on demand, but you don't really have control over how many of those things are going to fire. This is one reason why I find that these async calls are not best done in API calls, but actually are very good to use in like your background tasks. So that if you have a background task and you want to be running a lot of things through it, uh, your— if you use async, you can fire off 3, 5 things to all run at the same time, which allows things to go faster through your system. You're not just running them one after another after another, nor are you in a position where you have to kind of do the old-school hack, right, of getting them to go fire off in an API request. Right? You can now use async functions directly from background tasks, and that's something I find to be pretty handy because since background tasks are guaranteed to be serial, if you start using the async functions, you might multiply— hey, I'm running one thing at a time, running 5 things at a time. That's under control. When you have API requests coming in, you might get 100 requests all at the same time, and if you start using async functions, you might wind up with 500 actions all going at the same time, and that can be something that starts to bring down your system or requires you to spend a whole lot more money that you didn't you didn't need to have that level of parallelism going on on the on-demand basis. So consider where you're going to use this. Xano instances do have limited memory, and they do have limited CPU, and deciding how you're going to allocate that is one of the tricks to really getting the most value out of these systems. And the async functionality is a way for us to be able to cram even more juice out of these things, right, to be getting better value through being able to identify that these are functions that we want to run, that they're going to run in parallel, and that we don't need to wait for, you know, everything to run one after another after another, but rather be able to take advantage of parallelism because of asynchronicity on the one hand and being able to await these promises at the other. Hope you find this helpful. We do these kinds of hard things every day over at StateChange. And if you have a question or a comment, leave it in the— leave it down below. And if you like this kind of content, please do subscribe because we talk about the hard parts of no-code all day long.










