2

I'm building a SDK for an API using NodeJS, which can be found here. My problem is that when the user declare the module, it gives an username and password that I need to validate, and a token that must be used for future calls. So, this token is stored at irecarga.token, and for every future call I'll have to use it in order to identify the user. My problem is that if the user calls another function straight after the declaration, the declaration will probably not finish in time (because it does a HTTP POST) and the attribute token will be null.

// module declaration which requires a HTTP call and updates irecarga.token
var irecarga = require('../')({
    username: process.env.IRECARGA_USERNAME,
    password: process.env.IRECARGA_PASSWORD
})

// function called straight after declaration which uses irecarga.token
irecarga.getServiceProviders(48, function(err, data){
    // this code won't even run because the token = null will break the code
    console.log('err: ', err)
    console.log('data', data)
})

So, I saw plenty of solutions for creating blocking functions with Node, I could use callbacks or other modules that would require to send the functions I want to execute as parameter for other functions.

These solutions would most likely work, but the code will be ugly and messy. Besides, I don't think I'm innovating, actually this is the way I saw big companies like Microsoft and Google to declare their API keys.

Am I missing something here? Is there anything I could add inside of the validation function that would make any method of iRecarga wait until validation is done?

Ernani
  • 1,009
  • 3
  • 15
  • 26
  • Why don't you use promise. If you do not want it to be async, then use library like wait.for which makes your code execution sequential.But i will prefer to use promise any day. – maddygoround Oct 05 '16 at 02:03
  • Take a look at this: http://stackoverflow.com/questions/20315434/node-js-asynchronous-module-loading Answer describes several great patterns. Ultimately you'll have to use one of them, no way to avoid a callback (or promise as above comment suggests). But it does not necessarily have to make your code messy I don't think. – Levon Tamrazov Oct 05 '16 at 02:07
  • why you need to make a post request when setting the variables? – Orlando Oct 05 '16 at 03:40
  • @Orlando these variables are login and password, in order to check if they are valid I POST and address and get a token that I can use for future requests. – Ernani Oct 05 '16 at 19:30
  • @LevonTamrazov your link was helpfull thanks – Ernani Oct 05 '16 at 21:33

2 Answers2

1

In node.js, you don't make async things into blocking things. Instead, you use them as async and create an async interface on top of them.

So, you need to provide an async interface for your initialization so the caller knows when the initialization is done and when it is safe or possible to call other methods. There are a lot of different ways to do that:

  1. Return a promise from require()() with the resolved value being your module object. Then, the caller does a .then() and within that callback can use your properly initialized module.

  2. Pass a callback into the module initialization and require all use of the module to be from within that callback (same concept as the promise above, just using regular callbacks).

  3. Don't pass the credentials to the constructor. Instead, create an async .login() method that returns a promise and instruct the callers not to use the interface except within the resolved login promise.

For example, it could look like this:

require('../')({
    username: process.env.IRECARGA_USERNAME,
    password: process.env.IRECARGA_PASSWORD
}).then(function(irecarga) {
    // function called straight after declaration which uses irecarga.token
    // this method should probably be changed to use promises
    irecarga.getServiceProviders(48, function(err, data){
        console.log('err: ', err)
        console.log('data', data)
    });
}).catch(function(err) {
    // handle intiialization error here
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979
0

Using await you can add a one-liner to each of your API methods that will wait for the initializatoin (authentication) to complete by waiting for a promise to resolve. Here is one way you can do it. I use latest syntax with babel.

// myapi.js

import login from './auth';
import {query, insert} from './db';

let authenticated = null, user = null;

async function getProviders({regionId}) {
  await authenticated;
  return await query({region:regionId});
}

async function order({provider, service}) {
  await authenticated;
  return await insert({entity:'orders'}, {service, user});
}

export default function ({username, password}) {
  authenticated = new Promise( async (resolve, reject) => {
    const valid = await login({username, password});
    if (valid) {
      user = username;
      resolve();
    } else {
      reject();
    }
  });
  return {getProviders, order};
}

// test/myapi.js

import myapi from '../myapi';

async function test() {
  let api = myapi({username:'tom', password:'1234'});
  let providers = await api.getProviders({regionId:48});
  console.log(providers);
  let providers2 = await api.getProviders({regionId:5});
  console.log(providers2);
}

test().catch(console.error);
Jason Livesay
  • 6,317
  • 3
  • 25
  • 31
  • Answers are always better if they also include some explanation about the OP's issue rather than just code. – jfriend00 Oct 05 '16 at 06:06
  • I'm choosing this as a correct answer because await seems to be the best solution for my question, so the only requirement is that the user use await with the functions. However, I must note that I'm not doing this on my SDK, I decided that every function will check the token before run, and if it's null, the validation process will be called before running the function itself. – Ernani Oct 05 '16 at 21:32
  • @ErnanideSãoThiago that's a very bad requirement to use an SDK. Unless you are planning to use this in an environment you own, I don't think this is a good idea (ex. you won't be able to use this in a normal JavaScript environment). `async/await` is a ES2016 API, and is not supported without babel or other transpiler. You are better delaying the verification of the username/password until the first request, or just using Promises – Orlando Oct 06 '16 at 00:12
  • @Orlando - I agree with your comments. One correction is that async/await is ES7 syntax, not ES6. Yes, you need Babel to use it now. This particular issue seems like an API designed to be used today without Babel should be using promises which will extend just fine later to use async/await if desired. – jfriend00 Oct 06 '16 at 22:04
  • @jfriend00 ES2016 is the actual name for ES7. ES2016 !== ES6 – Orlando Oct 06 '16 at 22:06
  • @ErnanideSãoThiago - Blindly running the validation process before any first API call creates error handling issues for the callers as now you create a situation where lots of different API calls could all potentially return validation issues. That's doable, but not typically the simplest way for callers to write error handling which just leads to mistakes and poorly written code using your library. If you make an explicit step in the initialization of your library where the validation happens then callers will more likely code proper error handling for it. – jfriend00 Oct 06 '16 at 22:06
  • @Orlando - OK. I personally hate the year versions. For reasons, I don't know it's way easier for me to remember what ES6 and ES7 contain and most of the rest of the world (e.g. all the articles I read) seems to use the nomenclature ES6 and ES7 too. Why are they confusing things by creating two different ways to refer to the same thing and trying to switch to the year thing? – jfriend00 Oct 06 '16 at 22:08
  • You can use await in an API or library. It gets compiled to a promise or generator. One way is with the 'transform-runtime' babel plugin. They won't need babel to use your library. – Jason Livesay Oct 07 '16 at 04:43