Snake Eyes
Snake Eyes

Reputation: 16764

Use sequentially promise to run some ajax requests in Javascript and jQuery

I have steps array like:

var stepsToDo = [step1, step2, step3,...]

every step does ajax call:

function step1() { return $.ajax({ ... }); }
function step2() { return $.ajax({ ... }); }

The sample ajax call for step 1 (step 2 and others are similar):

return $.ajax({
            url: "test.php",
            type: "POST",
            dataType: "json",
            data: {"step-1": ""},
            beforeSend: function () {
                writeResult("Processing-> test 1");
            },
            success: function (result) {
                if (result) {
                    if (result instanceof Array) {
                        writeResult("Found: " + result.length + " items");
                        arr = result;
                    } else {
                        writeResult(result);
                        arr = [];
                    }
                }
            }
        });

function writeResult(str)  { console.log(str); }

I want to execute sequentially (defined in stepsToDo).

I tried like:

stepsToDo.reduce(
    (promise, method) => {
        return promise.then(method);
    },
    Promise.resolve()
);

Nothing happens, no print in console happens.

Why ?

Upvotes: 0

Views: 401

Answers (4)

Pablo
Pablo

Reputation: 6058

There most be something missing from the original question or the implementation. If all the step functions return the$.ajax promise then the Array.reduce pattern should work.

Below is a working prove of concept using a step() function to simulate the asynchronous code execution and using the exact same Array.reduce pattern:

// Step generator
function step(number) {
  return function() {
    return $.Deferred(function(def) {
      setTimeout(function() {
        console.log('Running Step ', number);
        def.resolve();
      }, Math.floor(Math.random() * Math.floor(250)));
    }).promise();
  }
}

// List of steps
var stepsToDo = [step(1), step(2), step(3), step(4), step(5)];

// Consume steps one at a time
stepsToDo.reduce(
  (promise, method) => {
    return promise.then(method);
  },
  Promise.resolve()
).then(() => console.log('All Done!'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Upvotes: 0

HMR
HMR

Reputation: 39270

I'm not sure what's wrong with the other answers but $.get (or ajax or post) will return a promise like.

So your step methods can look like this:

var stepOne = () => {
  return $.ajax({//just return the promise like
    url: "test.php",
    type: "POST",
    dataType: "json",
    data: {"step-1": ""}
  });
}

You can then reduce it to one promise that resolves with the results of the three steps:

[stepOne,stepTwo,stepThree].reduce(
  (p,fn)=>
    p.then(
      results=>fn().then(result=>results.concat([result]))
    ),
  $.Deferred().resolve([])
)
.then(
  results=>console.log("got results:",results),
  err=>console.warn("Something went wrong:",err)
);

You see I don't pass Promise.resolve as second argument to reduce but $.Deferred().resolve([]), this is jQuery promise like value. You can now support browsers that don't have native promises without the need to polyfill.

Although if you need to support those I'd recomment not using the arrow function syntax and use function(something){return something} instead of something=>something

Upvotes: 0

JJWesterkamp
JJWesterkamp

Reputation: 7916

For the reducer function you initially created...

stepsToDo.reduce((promise, method) => {
    return promise.then(_ => new Promise(method));
}, Promise.resolve());

...your step functions should resolve or reject the promises created in your reducer - therefore should implement the promise executor signature, I.E:

type executor = (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void;

For example:

function step1(resolve, reject) {
    $
        .ajax({ /* ... */ })
        .done(resolve)
        .fail(reject);
}

EDIT

As @Bergi states in the comments, this is an anti-pattern. Instead you could move the Promise construction into your step functions. A basic example (the jQuery ajax API still has to be converted to promises)

function step1() {
    return new Promise((resolve, reject) => {
        $.ajax({ /* ... */ }).done(resolve).fail(reject);
    });
}

If all your steps return you promises your reducer can get much simpler:

stepsToDo.reduce((promise, method) => promise.then(method), Promise.resolve());

You could in this case even just return the result of $.ajax, since jQuery promises do implement a then method:

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

If a native promise is required for error handling, i.e. Promise.catch, you can explicitly cast the jQuery promise to a native promise with Promise.resolve:

function step1() {
    return Promise.resolve($.ajax({ /* ... */ }));
}

Upvotes: 0

Bergi
Bergi

Reputation: 664548

Drop the new Promise. Your steps are functions that return promises, not promise executors - they never would call a resolve callback passed into them.

Upvotes: 1

Related Questions