Use Task for Asynchronous Actions

Brian Lonsdorf
InstructorBrian Lonsdorf
Share this video with your friends

Social Share Links

Send Tweet
Published 7 years ago
Updated 5 years ago

We refactor a standard node callback style workflow into a composed task-based workflow.

[00:00] Here, we have an application that will read a configuration file, then replace some contents, and write out a new one. It's really not that complicated, but it's full of all these error handling cases, and asynchronous callbacks, and whatnot.

[00:11] What we want to do is convert it to use tasks. We can compose these lines together. Let's go ahead and do that. What we need to do is wrap both read file and write file, the two asynchronous actions that have side effects. We want to wrap those in task.

[00:25] We can automate this. There are a lot of libraries, such as Futurize, that will do this for us. Let's do it manually so we can learn here. We have a read file that will take the same arguments as read file here, except for the callback.

[00:36] Let's just copy and paste this here. We want to take a file name and encoding. Then, we don't care about the callback here. We'll pass in the encoding here and the file name here. To make this lazy and give us a task, we have to use our task constructor around it.

[00:55] Here's where our callbacks come in. We have our reject case and our result case. In here, we can basically call, if there's an error, we'll reject it. If there is a success, we'll respond with the contents, just like that.

[01:07] That's all there is to it. We just put this task wrapped around our actual functionality here and pass our arguments into it. Now, let's do the same for write file. We use these as templates, and so it's pretty much the same.

[01:17] We have write file. We have to take our file names and contents here. Then, instead of read file, we just write the file. We take the file name, the contents, and the result here will be success or not. It will just return that.

[01:32] That's all there is to it. It's the same idea, the same template. We can do this for HTTP, and logging, and all sorts of side effect-y things that don't really compose well. Now that we have atomic pieces, our read file and our write file in task form, let's go ahead and attempt to rewrite app here. We'll leave this here, so we can look at it. We'll start with a new one.

[01:47] We have app where we will read the fileconfig.json -- there it is. Then, we'll pass in the UTF8, so it knows the coding. Then, we can just go ahead and map over that file since we have the contents here now, when we map over it, contents. Here, we want to replace the contents just like this line. Let's grab this piece and contents are placed.

[02:12] We're just going to take the eights out and put sixes in. If we want to look at what we're doing here, we have our little config.json here. Again, let's just put 888, and we're just going to replace those eights with sixes. That's not the point, the point is that we are going to read and write files and compose all these together in a peer way.

[02:27] Down here, with the write file, we have to do the same thing. Instead of mapping, because we're going to return another task, we want to chain. Now that we have the new contents here, we'll just go to write file with the config1.json, and those contents.

[02:42] That's all there is to that. That will write the file and return us a new task. Then, at the end of it all, we want to just fork it with a success. Why don't we do that outside of our application, because we don't want to log success buried somewhere deep in our function.

[02:55] We would never do that in a real application to pass logs somewhere deep in a library there. What we're going to do here is fork our application with the error case, with this console.log or error, and a success case which will be X, we'll say console.log success. That's it. It's all there is to it.

[03:13] Now, this will read the file, change the contents, write a new one out. We don't call it till down here. Now, one thing to notice is this whole thing is wrapped in a function, but it doesn't need to be. We could just remove that.

[03:23] An app is just a task that will go do all of this for us. You can use that as your discretion. Either one is fine. One creates a new task and the other one just re-uses the same old task here.

[03:32] Let's go ahead and call this. This is our task2.json. Then, it's a success here. That's great if we can cut our config.1.json. It has replaced the eights with sixes. It's nice and clean, composable, easy to read, and all of that.

Egghead
Egghead
~ 7 years ago

Hi, at the end of video you say one solution uses 'new' Task and other solution uses 'old' Task, can you elaborate a little bit, thanks.

Brian Lonsdorf
Brian Lonsdorfinstructor
~ 7 years ago

Hi!

If it's the part I'm thinking of - where make app a function or not:

app = () => new Task((rej, res)...
app().fork

vs

app = new Task((rej, res)...
app.fork

The idea is the former is a function that creates a new Task object instance each time you call it and the other is just assigned to the Task instance itself.

Since Task is lazy, it doesn't actually run until we fork it. That means we don't need to put it in a function wrapper to prevent it from running.

Egghead
Egghead
~ 7 years ago

Hi Brian,

Thanks for response.

I understand now, other question that I have is you could use Promises, right, instead of 'Task' library. Is there difference between those two, because I use Promises in the same way as Task is used here.

Brian Lonsdorf
Brian Lonsdorfinstructor
~ 7 years ago

Promises are eager, which means they are not pure - they will immediately run side effects upon construction. Task will allow you to work with values as though they were there, but it does not run until you tell it with fork() - usually outside your pure application.

Markdown supported.
Become a member to join the discussionEnroll Today