0

We have an application that is growing and our client side scripting needs to be refactored to be cleaner, leaner, and more maintainable. I'm attempting to take a small module a utilize Q library for promise chaining.

As you can see I need to pass some return values back from the initial function to the rest of the promise chain. Can someone please help me understand what I need to do to get the first function returning properly as a Promise? And then explain the chain?

Here is my starting point:

var promise = new Q.Promise(generateMoveRequest)
    .then(function (id) {
       var woNum = 12345;
       return layoutInit(woNum, id, true);
    }).then(function (result) {
        if (result) {
          return moveRequestInit(result, true);
        } else { 
          throw new Error('Template not loaded');
        }
    }).catch(function (err) {
        console.log(err);
    });

Generate Move Request:

generateMoveRequest: function () {
        $.ajax({
            method: "GET",
            url: window.contextPath + '/api/settings/getjson',
            data: {name: "the_name"},
            success: function (data) {
                if (data.length) {
                    var id = $.parseJSON(data).Parent;                                          
                    return id;
                }
            },
            error: function (xhr, textStatus, errorThrown) {
                console.log("xhr: ", xhr);
                return null;
            }
        });
    }

Layout Init:

layoutInit: function (num, id, appendLayout) {
    $.ajax({
            method: "GET",
            url: window.contextPath + '/some/url',
            data: {num: num, id: id},
            success: function (data) {
                return layoutInit.callback(data, appendLayout);
            },
            error: function (xhr, textStatus, errorThrown) {
                console.log("xhr: ", xhr);
            }
        });
    },
    callback: function (data, appendLayout) {
        if (data && data.toString().toLowerCase() !== "blank") {
            if (appendLayout) {
                $(data).insertBefore($("#detailsection"));
            } else {
                $("#detailsection").html(data);
            }                               
        } else {
            $("#detailsection").html('');
        }
    },

The generateMoveRequest function will execute but the chain never proceeds any further. No .then() execution and layoutInit never gets called.

I'm using the Q Library but some of the examples seem to leave out how to start/create the promise or turn the initial function into a Promise.

Can someone explain what I have wrong here or provide me with a clean example?

Encryption
  • 1,809
  • 9
  • 37
  • 52

2 Answers2

1

The jQuery implementation of promises does not follow the A+ standard, so you're better off with Q.

The promise must receive a function with 2 parameters - resolve and reject - which are also functions (remember that Javascript is a functional language). The resolve function must be called on success and reject on error. Whatever you pass to those functions will be available in the then() and catch() callbacks.

Long story short, rewrite your code like this:

var generateMoveRequest = function (resolve, reject) {
  $.ajax({
    method: "GET",
    url: window.contextPath + '/api/settings/getjson',
    data: {name: "the_name"},
    success: function (data) {
      if (data.length) {
        var id = $.parseJSON(data).Parent;
        resolve(id);
      } else {
        reject({message: 'Data not received'})
      }
    },
    error: function (xhr, textStatus, errorThrown) {
      reject({message: errorThrown})
  }
  });
}

var promise = new Q.Promise(generateMoveRequest)
    .then(function (id) {
       var woNum = 12345;
       return layoutInit(woNum, id, true);
    }).then(function (result) {
        if (result) {
          return moveRequestInit(result, true);
        } else {
          throw new Error('Template not loaded');
        }
    }).catch(function (error) {
      console.log(error.message)
    });

Later edit - see snippet to understand how Promises work:

// any number of params
function willPerformAnAsyncOperationAndReturnAPromise(firstParam, secondParam, thirdParam) {
  var promise = new Q.Promise(function (resolve, reject) { // always resolve & reject
    // perform an async operation, like waiting one second
    window.setTimeout(function () {
      if (firstParam !== 0) {
        // if the 1st param is not 0, it's a success
        resolve ({
          first: firstParam,
          second: secondParam,
          third: thirdParam
        })
      } else {
        reject({message: 'The first param must not be null'})
      }
    }, 1000)
  })

  return promise
}

willPerformAnAsyncOperationAndReturnAPromise(1, 2, 3)
  .then(function (data) {
    console.log(data)
  })
  .catch(function (error) {
    console.log(error)
  })

willPerformAnAsyncOperationAndReturnAPromise(0, 2, 3)
  .then(function (data) {
    console.log(data)
  })
  .catch(function (error) {
    console.log(error)
  })
motanelu
  • 3,945
  • 1
  • 14
  • 21
  • Thank you. Much appreciated! I now realize that any Promise function must have resolve and reject explicitly. Makes more sense now. Worked like a charm after that. – Encryption Apr 03 '17 at 20:34
  • It works, but it uses the [`promise constructor antipattern`](http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it), since `$.ajax` is already a promise. – trincot Apr 03 '17 at 20:35
  • 1
    Do note that as of jQuery 3.0, jQuery's implementation of Promises is [A+ conformant](https://promisesaplus.com/implementations#inside-frameworks). – Heretic Monkey Apr 03 '17 at 20:36
  • What if I have a function that is a promise but takes 2 arguments? Ex: generateMoveRequest(param1, param2). Does it need a wrapped to promise and chain? – Encryption Apr 03 '17 at 20:39
  • 1
    @Encryption In your case, the `generateMoveRequest` is the async operation that you wrap in a promise and will always take 2 parameters. I've added an explanation in the original answer for your to better understand. @trincot if the initial promise is not A+, it's not an antipattern. Better wrap it now in a compatible type as it will be easy to refactor later – motanelu Apr 03 '17 at 20:43
  • @motanelu thank you for the edit with extra example. – Encryption Apr 04 '17 at 12:26
0

Some issues:

  • Your function generateMoveRequest should return something: $.ajax returns a promise (it has a then method), so you can just return that;

  • You don't need to create a new promise, as generateMoveRequest returns one. This way you avoid the promise constructor anti-pattern.

Suggested code:

generateMoveRequest: function () {
    return $.ajax({
        method: "GET",
        url: window.contextPath + '/api/settings/getjson',
        data: {name: "the_name"},
    }).then(function (data) {
        if (data.length) {
            var id = $.parseJSON(data).Parent;                                          
            return id;
        }
    }).catch(function (xhr, textStatus, errorThrown) {
        console.log("xhr: ", xhr);
        return null;
    })
}

var promise = generateMoveRequest()
    .then(function (id) {
        var woNum = 12345;
        return layoutInit(woNum, id, true);
    }).then(function (result) {
        if (result) {
           return moveRequestInit(result, true);
        } else { 
           throw new Error('Template not loaded');
        }
    }).catch(function (err) {
        console.log(err);
    });
Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286