1

Simply put, I'm trying to dynamically generate an AJAX request based off a scenario that I'm retrieving via an AJAX request from a server.

The idea is that:

  1. A server provides a "Scenario" for me to generate an AJAX Request.
  2. I generate an AJAX Request based off the Scenario.
  3. I then repeat this process, over and over in a Loop.

The big idea is that I can change the second AJAX request dynamically, based off the Scenario given from the server.

I have this working, but I feel like the way I'm doing this is very messy. Is there any better way to go about thinking through this problem? Perhaps promises? If anyone could please review this and provide feedback or suggestions on how to clean it up--that would be greatly appreciated.

Here is my code: http://jsfiddle.net/6jph0e98/ (please open the console to see everything in action)

As a reference, here is the scenario data I'm currently working with:

    var scenario = {
        "base": {
            "frequency": "5000"
        },
        "endpoints": [
            {
                "method": "GET",
                "type": "JSON",
                "endPoint": "https://api.github.com/users/alvarengarichard",
                "queryParams": {
                    "objectives": "objective1, objective2, objective3"
                }
            }
        ]
    }
John Saunders
  • 160,644
  • 26
  • 247
  • 397
richie
  • 457
  • 1
  • 6
  • 19
  • 1
    Unlike forum sites, we don't use "Thanks", or "Any help appreciated", or signatures on [so]. See "[Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts). BTW, it's "Thanks in advance", not "Thanks in advanced". – John Saunders Feb 10 '15 at 03:30

1 Answers1

2

Here are my 2 cents: http://jsfiddle.net/3Lddzp9j/6/.

Yes, I think you can do this more elegantly by chaining promises. So I figured out what I think your app does, and how you can do it by chaining these promises. What is interesting that certain steps already return promises ( the jQuery AJAX calls ) but others don't. For those - we have to create our own promise that instantly resolves. And then there was the timeout which we wrapped in a promise.

Also, I tried to use some JS best practices, like keeping things out of the global space by wrapping them in an IIFE and applying the module pattern. This makes the overall control flow of your application nice and clean IMHO:

    var run = function() {
        getScenario()
        .then(mapToInstruction)
        .then(waitForTimeout)
        .then(callApi)
        .then(handleResults)
        .then(run);
    };

And also hides the private members and only exposes the run() method:

    return {
        // This will expose only the run method
        // and will keep all other functions private
        run : run
    }

Hope it helps - let me know what you think. Here's the full source, with comments:

// First of all - I'm using the javascript module pattern here
// this will all be much more easy once ES6 it out, but this will
// have to do for now.
// Also, I'm importing jQuery into the module as you can see, which
// is wrapped inside the IIFE ( Google it ) which keeps things nicely
// out of the global scope.
var App = (function ($) {

    // Gets the scenario from the API - $.get is just some syntactic
    // sugar for $.ajax with GET as method - NOTE: this returns a promise
    var getScenario = function () {
        console.log('Getting scenario ...');
        return $.get('http://demo3858327.mockable.io/scenario');
    };

    // The result of the previous promise is passed into the 
    // next as we're chaining. So the data will contain the 
    // result of getScenario
    var mapToInstruction = function (data) {
        // We map it onto a new instruction object
        var instruction = {
            method: data.endpoints[0].method,
            type: data.endpoints[0].type,
            endpoint: data.endpoints[0].endPoint,
            frequency: data.base.frequency
        };

        console.log('Instructions recieved:');
        console.log(instruction);

        // And now we create a promise from this
        // instruction so we can chain it
        var deferred = $.Deferred();
        deferred.resolve(instruction);
        return deferred.promise();
    };

    // This wraps the setTimeout into a promise, again
    // so we can chain it
    var waitForTimeout = function(instruction) {
        console.log('Waiting for ' + instruction.frequency + ' ms');
        var deferred = $.Deferred();
        setTimeout(function() {
            deferred.resolve(instruction)
        }, instruction.frequency); 
        return deferred.promise();
    };

    // Final step: call the API from the 
    // provided instructions
    var callApi = function(instruction) {
        console.log('Calling API with given instructions ...');
        return $.ajax({
            type: instruction.method,
            dataType: instruction.type,
            url: instruction.endpoint
        });
    };

    var handleResults = function(data) {
        console.log("Handling data ...");
        var deferred = $.Deferred();
        deferred.resolve();
        return deferred.promise();
    };

    // The 'run' method
    var run = function() {
        getScenario()
        .then(mapToInstruction)
        .then(waitForTimeout)
        .then(callApi)
        .then(handleResults)
        .then(run);
    };

    return {
        // This will expose only the run method
        // and will keep all other functions private
        run : run
    }
})($);

// ... And start the app
App.run();
Jochen van Wylick
  • 5,303
  • 4
  • 42
  • 64
  • This is awesome! This example actually really helped me understand how promises work and how to set them up in functions. Just one quick question on a failure case for a promise...is this how I would properly set it up? http://jsfiddle.net/jadn4mup/1/ – richie Feb 12 '15 at 19:46
  • @richie - I have to say that I also learnt a lot trying to figure it out. Glad you like it. Anyway - I didn't know this either but as it turns out - if one of the promises fails, the error nicely is forwarded down the promise chain to the first fail() - I updated the fiddle and I added one fail() to handle an error that happened in step 2: http://jsfiddle.net/3Lddzp9j/7/ . How you want to handle it - depends on you. Do you want to re-do the attempted thing? Or go back to the server to fetch new instructions? Depending on that - you need to change the code. – Jochen van Wylick Feb 12 '15 at 20:39
  • I've actually run into something that I can't quite solve in this problem. This JSFiddle, http://jsfiddle.net/3Lddzp9j/8/. Essentially, I'm having trouble retrofitting this code for a case in which the API returns two scenarios. I run into issues in the mapToInstruction function...where I'm not sure how to create two (or more) promises, and resolve each in individual promise chains. Was wondering if you had any ideas for how I might be able to do this. – richie Mar 02 '15 at 23:45
  • @richie - can the frequencies be different that you get back from the scenario call be different for both endpoints? If not - things will be pretty simple - else we need to rewrite things a bit more since I think we then need to branch the promise chain. - Also: will they 'sync' after execution, that is: will the fastest then wait on the slowest to get a new scenario - or will it continue to get a new one immediately? – Jochen van Wylick Mar 03 '15 at 10:12
  • Frequencies would always be the same for both endpoints, essentially--the loop won't restart until all of the data is retrieved. – richie Mar 03 '15 at 21:43
  • OK - I'll see if I can adapt the example tonight – Jochen van Wylick Mar 04 '15 at 13:07