Search code examples
javascriptnode.jspromisebluebirdchaining

How does bluebird know to proceed to the next `then` in this chain?


Having promisified fs-extra, I understand that I can use then to access the files. I guess there is some mechanism there that after it gets the file it knows to move to the next link in the then chain. However, the next then I've just put a simple for-loop. And the next one just prints a line to the console. I would expect some sort of Promise object to be returned for it to move down the chain. What is required to proceed down this chain? For example, if I put in a setTimeout for a second, then chain continues and prints out of order.

var Promise = require('bluebird')
  , fs = Promise.promisifyAll(require('fs-extra'))

fs.readdirAsync(dir)
    .bind(this)
    .then((files) => {
        this.files = files;
    })
    .then(() => {
        var num = 10000
        this.files.forEach((file, index, array) => {
            for(i = 0; i < num; i++){
                console.log(num*i);
            }
        });
    })
    .then(() => {
        console.log('middle of it');
    })
    .then(() => {
        console.log('done with it');
    })
    .catch((err) => {
        console.log(err);
    });

Solution

  • Each call to .then() returns a new promise that has its own .then() handlers. That promise is automatically linked to the previous promise so when the previous promise is completely done with it's own .then() handler and that .then() handler didn't return a promise or if it did when that promise it returned is resolved, it can then trigger the next promise in the chain to be resolved causing it to repeat the cycle for it's .then() handlers.

    The key is that p.then() returns a new promise that is itself resolved when p.then() is done so it can then trigger the next step in the chain p.then().then() and so on.

    Keep in mind that a .then() handler can do one of four things:

    1. Return nothing (same as returning undefined).
    2. Return a value
    3. Return a promise
    4. Throw an exception

    For the first two (returning a value), this just signifies that the .then() handler is done and then next one in the chain can be triggered now.

    When returning a promise, that promise itself is hooked with .then() handlers so it can be monitored. If/when it resolves, the chain continues. If/when it rejects, the chain is rejected. The promise that is returned may already be resolved or rejected or may be resolved or rejected in the future, no meaningful difference in behavior. If the returned promise is never resolved or rejected, the promise chain is stalled and will not continue until it is resolved or rejected (same as any promise).

    If the .then() handler throws an exception, this is caught by the .then() wrapper and it is automatically converted to a rejected promise with the exception as the reject reason.

    For example, if I put in a setTimeout for a second, then chain continues and prints out of order.

    You can't very effectively use setTimeout() by itself to delay a promise chain. Instead, you need to return a promise from a .then() handler that is resolved after some timeout time. Bluebird has .delay(x) that does that for you.

    Or, you can code one yourself if not using Bluebird:

    function delay(t) {
        return new Promise(function(resolve) {
            setTimeout(resolve, t);
        });
    }
    
    fn().then(function() {
        // delay promise chain by 1000ms
        return delay(1000);
    }).then(function() {
        // promise chain continues here
    });
    

    Or, with Bluebird promises, it's as simple as:

     fn().delay(1000).then(function() {
         delayed promise chain continues here
     });