If you program in modern JavaScript, you have probably replaced all your callbacks with Promises. Wrapping your mind around Promises requires some practice and even then there might be traps. I wrote a lot of blog posts about Promises, mostly to remind myself how to do certain actions using them.
Recently I have been watching the excellent (and hilarious) videos "Classroom Coding with Prof. Frisby" that show how to use functional JavaScript in the real world. Here are the links to part 1, part 2, and part 3. I have noticed how Professor Frisby is using data.Task to compose asynchronous functions that either resolve with a value, or reject with an error. For example, here is how the front end code is downloading or uploading data
1 | const {getJSON, post} = require('jquery') |
In this situation, Task
(from data.task)
looks a lot like a Promise
, doesn't it? What's the difference?
It cannot be that Task
is used here to nicely keep track of the possible error, I use
Promises for error values the same way.
The order of arguments is different: Task
expects the error handling callback at first
position, while Promise
puts it at the second one. But that is a tiny difference.
What is really different is when an operation runs. Let us compare both solutions on a simple
example. First, a timeout using a Task
1 | const Task = require('data.task') |
The code has lots of log statements for clarity. Let us run it
$ node index.js
returning new task
made task
Hmm, the code never set the timeout, and has never executed the timeout callback!
Let us replace Task
with Promise
and do the same.
1 | function getPromise() { |
Same code using ES6 Promise
implementation from Node 4.2.2
$ node index.js
returning new promise
setting promise timeout
made promise
Promise has finished
Interesting, the entire chain of actions fired right away. As soon as one has created a
Promise
object, the callback function passed to its constructor is executed, which
sets the timeout, etc. Which means that IF you have an instance of the Promise
- the
action has already started. It might have already finished! For example
1 | function getPromise() { |
$ node index.js
returning new promise
made promise Promise { 42 }
Even better to copy / paste this code into Chrome or any other modern browser that clearly shows the Promise state. The above code shows
returning new promise
made promise Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 42}
When does Task
execute its callback function? Only when someone calls task.fork
.
1 | const Task = require('data.task') |
$ node index.js
returning new task
made task
setting task timeout
Task has finished
The deferred execution has nice benefits. For example, you can compose tasks before running them. You just need to do it yourself.
1 | const Task = require('data.task') |
$ node index.js
returning new task
returning new task
made tasks
setting task timeout
Task has finished
setting task timeout
Task has finished
We used Task.chain
method from the first task to return the second task, which implicitly
calls task2.fork
method.
Just like Promise.resolve
is a shortcut for creating a function that resolves with a given
value, Task.of
can be used to start with a value.
1 | const Task = require('data.task') |
$ node index.js
made task
21
One can even use Task.map
to quickly pass the returned value through a composition
of callback functions.
1 | const Task = require('data.task') |
$ node index.js
made task
41
Task
is an excellent way to combine functions (async or not) first, before
starting executing them. This allows me to prepare for any possible side effects.
For example, if I need to test a DB query, it is very problematic with Promises
.
The mock DB has to be started before any code using DB runs.
1 | var db = require('db') |
During unit testing we must run the mock DB code before any code tries to load db-connection.js
otherwise it fails. But if we use Tasks
, we can write both production and testing code simply
like this
1 | // db-connection.js |
1 | // query.js (production code) |
1 | // query-spec.js (test code) |
I hope to start using the Task
monad in my code, it certainly seems to help control the
code execution.
Relevant
data.task source, part of Folktale suite of JavaScript libraries generic functional programming.
Another good tutorial showning Tasks in action is The Little Idea of Functional Programming by Jack Hsu