Come with me now on a journey through code and data...

Promises Promises

Sync / Async

A synchronous function is one that returns immediately:

In [1]:
function syncFn() {
    console.log('Sychronous call!')
}

The function will run before any following lines are run:

In [2]:
console.log('First')
syncFn()
console.log('Last')
First
Sychronous call!
Last

An asynchronous function sets up some logic to be run, but doesn't block following lines from being run while waiting for that logic to be executed:

In [3]:
function asyncFn() {
    setTimeout(function () {
        console.log('Asynchronous call!')
    }, Math.random() * 10000) 
}
In [4]:
console.log('First')
asyncFn()
console.log('Last')
First
Last
Asynchronous call!

Example Use

For my blog generator I need to load files from directories to process them. I'm using a library called `find` to get all files in a directory.

In [5]:
const find = require('find')
In [6]:
find.file('example', function(files) {
    console.log(files)
}).error(function(err) {
    if (err) {
      console.log(err)
    }
})
[ 'example/file1.txt', 'example/file2.html', 'example/file3.md' ]

This is asynchronous - meaning everything that relies on the files being loaded first must run inside the nested function. Things can easily get out of hand with continued nested functions.

A workaround could look something like the following:

In [7]:
find.file('example', function(files) {
    useFiles(files)
}).error(function(err) {
    if (err) {
      console.log(err)
    }
})

function useFiles(files) {
    console.log(files)
}
[ 'example/file1.txt', 'example/file2.html', 'example/file3.md' ]

But this also kind of has a long chain effect which could get confusing. You can't really return the file names and assign them to a const or variable to be used later like we are used to with synchronous functions.

Promises are used to write aynchronous code in a readable way:

In [8]:
function findFile(dir) {                                                                                                                                         
   return new Promise(function (resolve, reject) {                                                                                                                        
       find.file(dir, function(files) {
           resolve(files)                                                                                                                                                 
       }).error(function (err) {                                                                                                                                          
           reject(err)                                                                                                                                                    
       })                                                                                                                                                                 
   })                                                                                                                                                                     
}

var exampleFiles = findFile('example') // load the files list into a var
exampleFiles.then(console.log)
[ 'example/file1.txt', 'example/file2.html', 'example/file3.md' ]

Since we're just feeding the files or error into the resolve and reject functions and not doing anything with them beforehand we can rewrite this functionality in a slightly cleaner way:

In [9]:
function findFile(dir) {                                                                                                                                         
   return new Promise(function (resolve, reject) {                                                                                                                        
       find.file(dir, resolve).error(reject)                                                                                                                                                      
   })                                                                                                                                                                     
}

var exampleFiles = findFile('example')
exampleFiles.then(console.log)
[ 'example/file1.txt', 'example/file2.html', 'example/file3.md' ]

Let's test out the error functionality:

In [10]:
findFile('not a dir').then(console.log)
Error: not a dir does not exist.
    at Object.notExist (/home/minnie/workspace/node/node_modules/find/index.js:41:12)
    at traverseAsync (/home/minnie/workspace/node/node_modules/find/index.js:163:28)
    at /home/minnie/workspace/node/node_modules/find/index.js:282:7
    at process._tickCallback (internal/process/next_tick.js:61:11)

After Promises

Now we have this function wrapped in a Promise we can chain all sorts of stuff that rely on the list of files being loaded:

In [11]:
const path = require('path')
In [12]:
function getBasenames(files) {
    return files.reduce((returnVal, el) => {
         const ext = path.extname(el)
         return returnVal.concat(path.basename(el, ext))
    }, [])
}

function getExtensions(files) {
    return files.reduce((returnVal, el) => {
         return returnVal.concat(path.extname(el))
    }, [])
}

function getHtml(exts) {
    return exts.reduce((returnVal, el) => {
        if (el=='.html') {
            return returnVal.concat(el)
        }
        return returnVal
    }, [])
}
In [13]:
var myFiles = findFile('example')
var basenames = myFiles.then(getBasenames)
var htmlExtensions = myFiles.then(getExtensions).then(getHtml)

myFiles.then(console.log)    
basenames.then(console.log)
htmlExtensions.then(console.log)
[ 'example/file1.txt', 'example/file2.html', 'example/file3.md' ]
[ 'file1', 'file2', 'file3' ]
[ '.html' ]

I can't actually think of a case where getting all the html extensions is useful but that's besides the point. The point is you can chain then's and assign stuff to variables or constants to be used later. And when they're used they'll have had their value assigned from the asynchronous logic.

The end.