0

I am curious to know how can I make use of bluebird promises in my existing javascript code that makes use of lots of callback based mechanism. Here is the scenario:

In my web application on page load using jQuery, I will get page's main menu links and generate a TreeModel structure from them which I will use later on to show a Breadcrumb on page top.

The function I am using for generating this TreeModel is following:

function _traverseNodeChildren(selector, parentNode, callback1, callback2) {

    $(parentNode.model.element).find(selector).each(function(idx, elm) {

        // Create a Tree Node using TreeModel
        let node = _createTreeNode.apply(this.comp, [elm]);
        this.parentNode.addChild(node);

        let hasChildren = $(elm).find("+ul.dropdown-menu").length > 0;
        if (hasChildren == true)
            _traverseNodeChildren.apply(this.comp, ["+ul.dropdown-menu > li > a", node, callback1, callback2]);

        if (node.model.id == "aboutLink") // last node
        {
            setTimeout(() => {
                callback1.apply(this.comp, [callback2]);
            }, 100);
        }
    }.bind({parentNode: parentNode, comp: this}));
}

After above traversal completes, I want to call myServerCall function that will involve an async Ajax request and post to completion of this async request, finally I want to call a third function myFinalFunc.

At present I am using following code to make this traversal code execute:

const TreeModel = require('tree-model');
const _treeModel = new TreeModel();

let _bcRoot = _treeModel.parse({
    id: "0",
    href: null,
    text: "",
    element: $(".main-menu").get(0)
});

_traverseNodeChildren.apply(this, ["> li > a[data-bc-id]", 
            _bcRoot, myServerCall, myFinalFunc]);

But I would like it to be converted to bluebird promise based approach to get more control over it.

Following is the code that I want like it to be in the end:

_traverseNodeChildren.apply(this, ["> li > a[data-bc-id]", _bcRoot])
    .then(function() {
        return myServerCall();
    })
    .then(function() {
        return myFinalFunc();
    })
    .catch(function(error) {

    });

How can I do this using bluebird?

Faisal Mq
  • 5,036
  • 4
  • 35
  • 39
  • What is `callback1`, what is `callback2`? Why are you using `setTimeout` in `_traverseNodeChildren` at all, it doesn't appear to do anything asynchronous? – Bergi Dec 01 '16 at 19:20
  • Why all these `apply` calls? Aren't your targets instances that have the respective methods? – Bergi Dec 01 '16 at 19:21
  • @bergi This is in fact sort of pseudo code. Regarding callback1 n callback2 have u read my full question? These are `myServerCall` and `myFinalFunc`. And these two functions will be async – Faisal Mq Dec 01 '16 at 19:26
  • I just wondered because it doesn't make sense to pass two `callback` parameters to a function. Have a look at my (preliminary) answer. – Bergi Dec 01 '16 at 19:30
  • What parts exactly are pseudo code? The `setTimeout`? And what is this "`// last node`" comment about, do you want to imply there is only one such node (and that you now it is in the last place)? – Bergi Dec 01 '16 at 19:31

1 Answers1

1

I'll start with simplifying your callback code to

function traverseNodeChildren(comp, selector, parentNode, callback) {
    $(parentNode.model.element).find(selector).each(function(_, elm) {
        // Create a Tree Node using TreeModel
        let node = createTreeNode(comp, elm);
        parentNode.addChild(node);

        let hasChildren = $(elm).find("+ul.dropdown-menu").length > 0;
        if (hasChildren)
            _traverseNodeChildren(comp, "+ul.dropdown-menu > li > a", node, callback);

        if (node.model.id == "aboutLink")
            setTimeout(function() {
                callback(comp);
            }, 100);
        }
    });
}

traverseNodeChildren(this, "> li > a[data-bc-id]", _bcRoot, function(comp) {
     myServerCall.call(comp, myFinalFunc)
});

However, given that callback is potentially called multiple times (when there are multiple aboutLinks), you cannot really convert this to promises. Unless you want it to behave differently.
If there is only one aboutLink, you don't need the asynchrony and callbacks at all in your traversal function. Just do

function traverseNodeChildren(comp, selector, parentNode) {
    $(parentNode.model.element).find(selector).each(function(_, elm) {
        // Create a Tree Node using TreeModel
        let node = createTreeNode(comp, elm);
        parentNode.addChild(node);

        let hasChildren = $(elm).find("+ul.dropdown-menu").length > 0;
        if (hasChildren)
            _traverseNodeChildren(comp, "+ul.dropdown-menu > li > a", node);
    });
}

traverseNodeChildren(this, "> li > a[data-bc-id]", _bcRoot);
setTimeout(function() {
     myServerCall(myFinalFunc)
}, 100);

which you now easily can convert to bluebird without even touching traverseNodeChildren:

traverseNodeChildren(this, "> li > a[data-bc-id]", _bcRoot);
Promise.delay(100)
.then(myServerCall)
.then(myFinalFunc)
.catch(function(err) { … });

If you want some kind of delayed traversal that waits on every node, you can use

function traverseNodeChildren(comp, selector, parentNode) {
    return Promise.mapSeries($(parentNode.model.element).find(selector), function(elm) {
        // Create a Tree Node using TreeModel
        let node = createTreeNode(comp, elm);
        parentNode.addChild(node);

        let hasChildren = $(elm).find("+ul.dropdown-menu").length > 0;
        if (hasChildren)
            return _traverseNodeChildren(comp, "+ul.dropdown-menu > li > a", node);
        else
            return Promise.delay(100);
    });
}

traverseNodeChildren(this, "> li > a[data-bc-id]", _bcRoot)
.then(myServerCall)
.then(myFinalFunc)
.catch(function(err) { … });
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • There is only one aboutLink on page's main menu and it will be the last node in traversal. – Faisal Mq Dec 01 '16 at 19:44
  • Then why did you give the traversal a callback at all? Just sychronously traverse the menu, and afterwards start the timeout. – Bergi Dec 01 '16 at 19:54
  • Btw, did you really need to pass `this.comp` into the callback at all? – Bergi Dec 01 '16 at 20:00
  • Yes your this point makes sense. Jquery work will b synchronous. I will fix this but let's suppose if there is some other async call from within node traversal, then for that what we will do? – Faisal Mq Dec 01 '16 at 20:09
  • I guess 'this' context changes in jquery's click handler to the clicked target. – Faisal Mq Dec 01 '16 at 20:16
  • If there was something asynchronous in the traversal, you'd simply use `Promise.map` instead of the `.each` function, and return a promise from the callback. – Bergi Dec 01 '16 at 20:17
  • @FaisalMushtaq I don't see what any of this has to do with `click` handlers? – Bergi Dec 01 '16 at 20:19
  • Prmoise.map. That's the point what I needed. Can u plz show this in your answer. Thanks – Faisal Mq Dec 01 '16 at 20:19