1

I was trying to search for an answer, but most of the hits I'm getting are for either no parameters or fixed parameters.

What I would like to be able to do is implement an optional callback in a javascript function with an unknown number of parameters and parameter types.

I'm implementing a javascript layer to use AWS temporary credentials to manipulate files on S3. Some function calls upload files (receiving an array), some delete files (also array), some rename a bucket object (with two parameters, old key and new key), etc.

The problem is, when using temporary credentials, the credentials can time out. So before any call is done, I have to check if the credentials need updating. If they do, I have to do an asynchronous call to get the credentials first. Only when the credentials have been retrieved, should it start doing whatever it was asked to do.

Is this what those 'promises' were all about? I haven't worked with those much yet and will be doing reading on them now, but wouldn't have a clue how to implement one (yet).

Basically, in my code, I need a wrapper for any given aws function:

function onDelete(key) {
    if(AWS.config.credentials.needsRefresh()) {
        refreshToken(); // makes an asynchronous call with jquery
        // need to do actual delete  when ajax finished: delete(key)
    } else {
        // do actual delete immediately: delete(key)
    }
}
function onRename(oldkey,newkey) {
    if(AWS.config.credentials.needsRefresh()) {
        refreshToken(); // makes an asynchronous call with jquery
        // need to do actual rename when ajax finished: rename(oldkey, newkey)
    } else {
        // do actual rename immediately: rename(oldkey, newkey)
    }
}

Or something along those lines. Any help is appreciated.

Scott
  • 7,983
  • 2
  • 26
  • 41
  • Have you tried the spread operator? – Rohit Kashyap Aug 20 '19 at 18:30
  • In javascript arguments are always optional, you can pass more or less than specified. You can access all arguments using the `arguments` variable – Wendelin Aug 20 '19 at 18:37
  • @wendelin which kills performance though and makes your code generally hard to read, so I recommend to avoid that if possible. – Jonas Wilms Aug 20 '19 at 18:38
  • @JonasWilms I am not sure how it kills performance, but I agree that you should avoid it. I just wanted to make him aware of the fact that arguments are always optional – Wendelin Aug 20 '19 at 18:41
  • So `AWS.config.credentials.needsRefresh()` responds synchronously? – Roamer-1888 Aug 20 '19 at 19:48
  • @wendelin `arguments` itself is not that bad itself, but depending on the usage, e.g. iterating it or the worst if it lives longer than the scope (by returning it), it completely kills JIT compiling (at least it did on V8) – Jonas Wilms Aug 20 '19 at 20:02
  • Wow, so many good answers - how do I pick just one? I managed to get something working with a simple .then() but I'm going to go over this thread when I get to work an improve on it. Looks like turtle gave the most alternatives, so he gets the check. – Scott Aug 22 '19 at 13:28
  • thanks guys! I appreciate it! – Scott Aug 22 '19 at 13:29
  • @Roamer-1888 the needsRefresh is actually not as smart as it would seem. You have to initialize an 'expireTime' when creating the credential object (or at least the way I'm using it I have to) and it just checks against that time with an optional offset window. All local calculation, simple math. – Scott Aug 23 '19 at 16:13
  • @Scott, sounds like it's synchronous anyway. At the topmost level, all that matters in program design is knowing (or determining) what is synchronous and what is asynchronous. After that, coding all about achieving the desired side effects and/or return values (and doing so efficiently). – Roamer-1888 Aug 24 '19 at 05:42

2 Answers2

2

You can use promises or callbacks for that:

Callback-based:

function refreshToken(callback) {
  $.ajax({
    success: callback,
    //....
  });
}

function deleteKey(key, callback) {
  // do something asynchronous here, then call callback
  callback({some: 'value'});
}

function onDelete(key, callback) {
    if(AWS.config.credentials.needsRefresh()) {
        refreshToken(() => {
           deleteKey(key, callback);
        });
    } else {
        deleteKey(key, callback);
    }
}


// then you would use it like this:
onDelete("some-key", result => {
  console.log(result); // will be {some: 'value'}
});

If can use promises & async / await this becomes a lot nicer:

async function refreshToken() {
  return $.ajax({/* ... */});
}

async function deleteKey(key) {
  // delete the key
}

async function onDelete(key) {
    if(AWS.config.credentials.needsRefresh())
        await refreshToken();
    return deleteKey(key);
}


// then you would use it like this:
onDelete("some-key").then(result => {
  console.log(result); // will be {some: 'value'}
});
// or within an async function:
let result = await onDelete("some-key");

To make the code more readable / nicer you could use a HOF (higher-order function):

async function refreshToken() {
  return $.ajax({/* ... */});
}

// higher order function
function withTokenRefresh(fn) {
  return async (...args) => {
    if(AWS.config.credentials.needsRefresh())
      await refreshToken();
    return fn(...args);
  };
}

const onDelete = withTokenRefresh(async key => {
  // todo: delete it
});

const onRename = withTokenRefresh(async (oldkey, newkey) => {
  // todo: rename it
});

if you're using a transpiler like babel, you could also use decorators (a fancier way for using HOFs):

async function refreshToken() {
  return $.ajax({/* ... */});
}

// decorator
function WithTokenRefresh(fn) {
  return async (...args) => {
    if(AWS.config.credentials.needsRefresh())
      await refreshToken();
    return fn(...args);
  };
}

@WithTokenRefresh
async function onDelete(key) {
  // todo: delete it
}

@WithTokenRefresh
async function onRename(oldkey, newkey) {
  // todo: rename it
}
Turtlefight
  • 9,420
  • 2
  • 23
  • 40
  • The [HOF](https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad) article is very slightly dodgy. The author believes that "Functional Programming is a form of programming in which you can pass functions as parameters to other functions and also return them as values", which is not even close. He may carry on to say good things; I don't know because I stopped reading at that point. – Roamer-1888 Aug 24 '19 at 15:20
  • 1
    @Roamer-1888 you're right, i replaced the link with a better one :) – Turtlefight Aug 25 '19 at 03:07
  • lol, wow, he dost assume a lot. Whether I believe it or not, I can only follow practices that are put in use elsewhere to achieve the kind of results I need. If you are aware of a better way, that is part of the reason I asked the question. No need to condescend or be an ass. That's what these forums are for - asking such questions. Not getting prejudgments and editorial snipes. Excuse me because I do not program for AWS every day. Other systems I control allow different solutions that don't require waiting for renewing expired credentials based on other people's rules. – Scott Aug 27 '19 at 18:53
0

With spread and async / await, you could build something like that:

  const withCredentials = task => async (...args) => {
        if(AWS.config.credentials.needsRefresh())
          await refreshToken();
       return task(...args);
  } 

to be used as:

 const onRename = withCredentials(async (oldKey, newKey) => {
   //...
 });

onRename("some", "values")
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151