nodejs: how to Scope Constant Variables when using async.map()

I am using node js code as below i need to use some values from key/value maps basically of static nature.

var svr = http.createServer(function(req, resp) {

  // Directly accessing all variables and KVM's here.    
  KVM_ContentURL = apigee.getVariable(req, 'ContentURL');  
  authorization = apigee.getVariable(req, 'authorization');
  ....
  async.map(urls, getContent, function(err, res) { ..}

}

I need to use this information then in a fuction outside the scope of block above. Here i need the KVM variables authorization. I am using async.map to perform an operation on all URLs and pass to getcontent().

// fetch the content
function getContent(url,callback) {
   var options = {
     uri: url,
     headers: { 'Authorization':authorization }
   }
   ...
}

what should be the scope of variables? if i mark them as const/var/let they are out of scope. Also, i do not want to keep repeating the KVM fetch calls on each function calls. inside getContent().

0 5 2,819
5 REPLIES 5

Good question. It is just a pure JavaScript question, not related to anything that is Apigee specific.

I suggest that you use a closure. You can look it up and read all about the idea if you like. But you don't need to do further reading, in order to be able to use the idea.

The idea here is: invoke a function, passing the values you want to preserve. Have that function return a function. An example:

function returnFunctionThatReturnsSomething(theArgument) { 
  return function() { 
    return theArgument;
  };
}
var fn5 = returnFunctionThatReturnsSomething(5); 
var result = fn5(); 
console.log(result);  // 5
var fnFoo = returnFunctionThatReturnsSomething('foo'); 
result = fnFoo(); 
console.log(result);  // 'foo'

Do you see? The return value of returnFunctionThatReturnsSomething is a function. What does that function return? it returns the value of the parameter passed to returnFunctionThatReturnsSomething . This happens via a closure.

We can use the same 'closure' technique to accomplish what you want.

var svr = http.createServer(function(req, resp) {

  // Directly accessing all variables and KVM's here. 
  var contentURL = apigee.getVariable(req, 'ContentURL');  
  var authorization = apigee.getVariable(req, 'authorization');
  ....
  async.map(urls, getContent1(contentURL, authorization), function(err, res) {
   ...
  }); 

}

And the getContent1 is a function that returns a function that is shaped like your original getContent. It should look like this:

// fetch the content
function getContent1( outerUrl, authorization ) {

  return function getContent( innerUrl, callback ) {
     var options = {
       uri: url,  // not sure if innerUrl or outerUrl is intended here
       headers: { 'Authorization':authorization }
     }
     ...
  };
}

See?

thanks @Dino

though let me first confess stupidity of my question that I could have declared the variables outside the block just before the line below, so they are available across the functions in a more global scope.

var svr = http.createServer(function(req, resp){

. But, then i wouldn't have come to know of this technique. It seems to be working. fine and seems more compact.

yes, you could use global variables. Maybe a better approach!

Also - be aware that with the code I suggested, the anonymous function inside createServer() will instantiate new contentURL and authorization variables get initialized with every request.

Not applicable

I understand the need to use async.map for executing requests in parallel, and my answer won't address what you're looking for directly. For that, I think Dino's solution is a good example of it. However, I'd suggest considering using Promises and more specifically Promise.all to implement this requirement instead of async module.

Here's an example of an Apigee Node.js API Proxy. Here's a snippet:

Full source code. https://github.com/dzuluaga/apigee-tutorials/tree/master/apiproxies/parallel-nodejs-api-proxy-with-p...

app.get('/albums', function (req, res, next) {
  try {


    // get an array of promises of artists. Each artist to be passed as: e.g. artists[]=radiohead&artists[]=phoenix
    Promise.all(req.query.artists
        .map(function (artist_name) {
          return getAlbum(artist_name);
        }))
      .then(function (all_artists) {


        //convert each resolved promised into JSON and convert it into an array.
        res.json(all_artists.map(function (artist) {
          return JSON.parse(artist)
        }));
      })
  } catch (e) {
    res.send(e.message);
  }
});


// this is the function that makes the call to the backend. Note usage of request promise
function getAlbum(artist) {
  var options = {
    uri: 'http://lyrics.wikia.com/api.php',
    qs: {
      artist: artist,
      fmt: 'json',
      action: 'lyrics'
    }
  };
  return rp(options);
}

thanks Diego Zuluaga

i did try this earlier, but then we found that we will have to import lot of node_modules that come with promise. In most other proxies in our project we haven't done that as we have been OK with default modules. Thus, carrying forward the same idea we didn't use promise as done in the example.