0

I am using node-soap to integrate with an external soap-based API. With this library, a client object is created at runtime based on the WSDL. Therefore, the soap client object is not valid at design time. This is a problem trying to use the Q promise library. The Q library is binding/evaluating the method call too early, before it is defined. Here is the code snippet to illustrate.

This is the client code using a promise chain:

innotas.login(user,pass)
.then(innotas.getProjects())
.then(function () {
    console.log('finished');
});

This is the service code snippet for login() which works fine.

this.login = function login (user, password) {
    var deferred = q.defer();

    // some stuff here

    return self.createClient()
    .then(function () {
        self.innotasClient.login({ username: self.innotasUser, password: self.innotasPassword }, function(err, res) {
            if (err) {
              console.log('__Error authenticating to service: ', err);
              deferred.reject(err);
            } else {
              self.innotasSessionId = res.return;
              console.log('Authenticated: ', self.innotasSessionId);
              deferred.resolve();
            }
        });
    });
};

This is the problem. self.innotasClient.findEntity does not exist until after CreateClient()

this.getProjects = function getProjects (request) {

    // initiation and configuration stuff here

    // CALL WEB SERVICE
    self.innotasClient.findEntity(req, function findEntityCallback (err, response) {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve(response);
      }
    })

    return deferred.promise;

    // alternate form using ninvoke
    return q.ninvoke(self.innotasClient, 'findEntity', req).then(function (response) {
        // stuff goes here
    }, function (err) {
        // err stuff goes here
    })
}

This is the runtime error:

        self.innotasClient.findEntity(req, function findEntityCallback (err, r
                           ^
TypeError: Cannot call method 'findEntity' of null
    at getProjects (/Users/brad/Workspaces/BFC/InnotasAPI/innotas.js:147:28)
    at Object.<anonymous> (/Users/brad/Workspaces/BFC/InnotasAPI/app.js:13:15)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)

This code works fine with callbacks. Any idea how to get this to work with the promise library?

bschulz
  • 191
  • 2
  • 12
  • You would have a fighting chance with `.then(innotas.getProjects)`, ie pass the function itself, not the value it returns when executed. – Beetroot-Beetroot Feb 25 '14 at 05:17
  • Passing the value in the return as suggested by @beri below does seem to resolve this problem. Now I'm getting the code executing out of order (see below comment). – bschulz Mar 01 '14 at 12:22

1 Answers1

1

The Q library is binding/evaluating the method call too early, before it is defined

No - you are:

innotas.login(user,pass)
.then(innotas.getProjects())

Here you're calling getProject() before passing its results into the then method. However, then does expect a callback function, which will be called when the promise if resolved. You'd use

innotas.login(user,pass).then(function(loginresult) {
    innotas.getProjects()
})

If the client does not exist until the createClient method yields, the client should be the result of that function - let it return a promise for the client!

Your lib should then look like this:

this.login = function login (user, password) {
    // some stuff here
    return self.createClient().then(function(client) {
        return Q.ninvoke(client, "login", {
            username: self.innotasUser,
            password: self.innotasPassword
        }).then(function(res) {
            self.innotasSessionId = res.return;
            console.log('Authenticated: ', self.innotasSessionId);
            return client;
        }, function(err) {
            console.log('__Error authenticating to service: ', err);
            throw err;
        });
    });
};
this.getProjects = function getProjects (client) {
    // initiation and configuration stuff here

    // CALL WEB SERVICE
    return q.ninvoke(client, 'findEntity', req).then(function (response) {
        // stuff goes here
    }, function (err) {
        // err stuff goes here
    });
};
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you for your answer! Returning the client solves the problem where the client object is reference before it is defined. Now this introduced another strange problem where the 3rd then() seems to be called before the second then() is completed. Here is a gist to show the code: https://gist.github.com/sfguy/9288957 – bschulz Mar 01 '14 at 12:17
  • Not only should it wait for the login, it should also log the `client` instead of `undefined`. This helps to identify your problem: You're not returning the client from the `then` callback that does the login. Use `.then(function (client) { return innotas.login(client, user, pass); }) // resolves with the 'client' when login is done` – Bergi Mar 01 '14 at 12:21
  • I think I solved my own problem. According to the Q Wiki Page https://github.com/kriskowal/q#chaining nested calls are the same as chained calls. However, in practice this does not seem to be the case. By nesting rather than chaining things are executing in order: `.then(function (client) { innotas.login(client, user, pass) .then(function (client) { console.log("calling projects..."); innotas.getProjects(client); }) })` – bschulz Mar 01 '14 at 12:44
  • Chained calls are the same if your **`return`** the promise from the callback, which you didn't. Maybe I should've highlighted that better in above comment. – Bergi Mar 01 '14 at 12:47
  • Catching on now...makes perfect sense to return innotas.login() from the callback. Thank you!! – bschulz Mar 01 '14 at 13:20