0

I try to write a function, that returns a promise version for some API (AgileCRMManager). The design of the api works pretty similar to request.

But i have some Problems with the handover of the Function. The Function have no access to the prototype of its own. I got following log output:

[Function: getContactByEmail]
[Function: getContactByEmail]
TypeError: this.getOptions is not a function
    at getContactByEmail (/Users/Tilman/Documents/Programme/NodeJS/async_test/node_modules/agile_crm/agilecrm.js:116:24)
    at /Users/Tilman/Documents/Programme/NodeJS/async_test/routes/portal.js:30:5
    at restPromise (/Users/Tilman/Documents/Programme/NodeJS/async_test/routes/portal.js:29:10)
    at Object.<anonymous> (/Users/Tilman/Documents/Programme/NodeJS/async_test/routes/portal.js:22:1)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:456:32)
    at tryModuleLoad (module.js:415:12)
    at Function.Module._load (module.js:407:3)
    at Function.Module.runMain (module.js:575:10)

This is the part with getOptions from agile_crm:

ContactAPI.prototype.getOptions = function getOptions() {
    this._options = {
        host: this.domain,
        headers: {
            'Authorization': 'Basic ' + new Buffer(this.email + ':' + this.key).toString('base64'),
            'Accept': 'application/json'
        }
    };

    return this._options;
};

This is my Code (if i change restFunction with a.contactAPI.getContactByEmail, it works. But i want to have it for more functions):

var AgileCRMManager = require("agile_crm")
var a = new AgileCRMManager("user-domain",
        "api-key",
        "user-mail")

restPromise('ds@umzuege-selisch.de',a.contactAPI.getContactByEmail)
  .then(console.log)
  .catch(console.error)

function restPromise(data, restFunction) {
  console.log(restFunction);                    // => [Function: getContactByEmail]
  console.log(a.contactAPI.getContactByEmail);  // => [Function: getContactByEmail]
  return new Promise(function(fulfill, reject){
    //a.contactAPI.getContactByEmail(
    restFunction(
      data,
      function(data){
        fulfill(data)
      },
      function(error){
        reject(new Error(error))
      }
    )
  })
}

Any Idea how i can hand Over the function and api will still work?

Til
  • 95
  • 1
  • 1
  • 8
  • I don't see anywhere where you're calling getOptions. Since the value of `this` depends on how you call a function (not on how you define it) we can't help you if you don't show how you're calling getOptions – slebetman Feb 20 '17 at 17:28
  • i do not call getOptions in my code. The function which i use provided by the library [agile_crm](https://github.com/agilecrm/nodejs) calls getOptions. The Code with with the function getOptions is from the file under node_modules/agile_crm wich i require in my code. – Til Feb 20 '17 at 21:07
  • OK. I see what's going on. – slebetman Feb 21 '17 at 04:30

1 Answers1

1

You've misunderstood how this works in javascript. Normally I'd vote to close questions like this but this seems to be a specific use case that requires further explanation. For a full explanation of how this works I'd suggest reading the answer from this question: How does the "this" keyword in Javascript act within an object literal?.

The Theory

Now, the problem is that in js the value of this depends on how you call a function. If a function is a method, this resolves to the object the method belongs to:

a.foo = function () { console.log(this) };
a.foo() // should output `a`

But if the function is a simple function that is not part of an object then this is either the global object (window in browsers) or undefined depending on strict mode. Note that js interprets weather or not a function is a method when you call the function. Not when you define the function. This has interesting consequences:

a.foo = function () { console.log(this) };
b = a.foo;
b(); // here b is a simple function because it's not something.b
     // so the output is either the global object or undefined

The Bug

This is what happens in this line of code:

restPromise('ds@umzuege-selisch.de',a.contactAPI.getContactByEmail)

You're passing a.contactAPI.getContactByEmail as a function reference. Therefore it loses the fact that it's part of a.contactAPI and instead becomes a simple function. This fact can be clearly seen by how you call the function:

restFunction( //...

Note that you're calling restFunction which is a function reference to a.contactAPI.getContactByEmail. Since it is called simply as restFunction() instead of a.contactAPI.restFunction() the this in the function points to the global object (or undefined depending on strict mode) instead of a.contactAPI.

Fixes

There are several ways you can fix this.

  1. Wrap a.contactAPI.getContactByEmail in an anonymous function so you can call it with the proper this:

    restPromise(
      'ds@umzuege-selisch.de',
      function(data,ok,err){
        a.contactAPI.getContactByEmail(data,ok,err);
      }
    )
    
  2. Use .bind():

    restPromise(
      'ds@umzuege-selisch.de',
      a.contactAPI.getContactByEmail.bind(a.contactAPI)
    )
    
  3. Modify restPromise() to accept an object instead of a function.

Community
  • 1
  • 1
slebetman
  • 109,858
  • 19
  • 140
  • 171