lgc_ustc
lgc_ustc

Reputation: 1664

Recursion with Q promises

I have a scenario where recursion occurs with promises, and I need some help on this.

I have an array that contains a sequence of messages. Each message can be either: 1 primitive, like a string; or 2 compound, which is just a pointer to a DB area that contains a sequence of messages. For example, the array can be: ['hello', (compound), 'there'], where (compound) can contain ['how', 'are', 'you'] when the compound message is fetched from DB. Therefore, the final 'expanded' array looks like: ['hello', 'how', 'are', 'you', 'there']. To completely convert an array of messages into an array of primitive messages is called 'expand'.

Note that a message in a compound message can be also a compound message, this is where recursion gets in. For instance, if (compound) in the above example is ['how', 'are', 'you', (lower level compound)], and (lower level compound) is ['Tom', 'Jerry'], then (compound) will be expanded into ['how', 'are', 'you', 'Tom', 'Jerry'], and the original array will be expanded into ['hello', 'how', 'are', 'you', 'Tom', 'Jerry', 'there'].

Here is what I think the code looks like, without promises:

function expandMessages(messages, outputMessages) {
    messages.forEach(function(message) {
        if (message.primitive) {
            outputMessages.push(message);
        }
        else {
            var fetchedMessages = fetchMessages(message);
            expandMessages(fetchedMessages, outputMessages);
        }
    });
}

In the above code, fetchMessages fetches messages from DB for a compound message.

How should I promisify the above code, so that after the outer promise returns: 1 outputMessages contains only primitive messages; and 2 correct message order is maintained, that is, any compound message's 'submessages' are inserted to where the compound messages were in the original array.

Thanks!

Upvotes: 0

Views: 66

Answers (1)

Roamer-1888
Roamer-1888

Reputation: 19298

The only reason to involve promises is when fetchMessages() is asynchronous, so let's assume that to be so.

The code is surprisingly simple :

function expandMessages(messages) {
    return Q.all([].concat(messages).map(function(m) {
        return Array.isArray(m) ? fetchMessages(m).then(expandMessages) : m.then ? m.then(expandMessages) : m;
    })).then(flatten);
}

where flatten() is :

function flatten(list) {
    return list.reduce(function(a, b) {
        return a.concat(Array.isArray(b) ? flatten(b) : b);
    }, []);
};

DEMO

Explanation

  • [].concat(messages) is a safety measure to guard against messages not being Array, in which case messages.map() would throw.
  • .map(...) maps a mixed array of Strings/Arrays/Promises to a mixed Array of Strings and Promises.
  • m.then(expandMessages) and fetchMessages(m).then(expandMessages) cause recursion to happen.
  • Q.all() aggregates the mixed array of Strings and Promises, and delivers a mixed array of Strings and Arrays.
  • .then(flatten) reduces the mixed array of Strings and Arrays to an array of Strings.
  • the order of the final array is as you want because Q.all(messages.map(...)) delivers (at each level) an array which is congruous with messages.
  • As you will see in the demo, a "compound message" can be either Array or promise-wrapped Array, which gives a degree of flexibility you may or may not need (but it comes free).

Upvotes: 1

Related Questions