0

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 ?

Snake Eyes
  • 16,287
  • 34
  • 113
  • 221

4 Answers4

1

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

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

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({ /* ... */ }));
}
JJWesterkamp
  • 7,559
  • 1
  • 22
  • 28
  • [No, they should not. They should return promises!](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it) – Bergi Feb 04 '18 at 13:16
  • @Bergi I updated my answer. Do you know a better approach for **$.ajax** to **promise** API? – JJWesterkamp Feb 04 '18 at 13:36
  • 1
    jQuery's `ajax` already does return (jQuery) promises - you don't need to do anything! If you want to make native promises, [just use `Promise.resolve` on them](https://stackoverflow.com/a/31327725/1048572). – Bergi Feb 04 '18 at 13:39
  • I think it's worth noting that jQuery promises **do not** implement `catch` afaik. – JJWesterkamp Feb 04 '18 at 13:45
  • They do since v3, but yeah jQuery's [support for promise details has been below par](https://stackoverflow.com/q/23744612/1048572). – Bergi Feb 04 '18 at 13:56
0

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

HMR
  • 37,593
  • 24
  • 91
  • 160
0

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>
Pablo
  • 5,897
  • 7
  • 34
  • 51
  • And how can I stop at specific `step` the exceution ? – Snake Eyes Feb 04 '18 at 15:38
  • @SnakeEyes That is a different question. If the promise of any `step` is rejected it will break the chain and stop other steps from executing. In your case if any ajax request returns a none `2xx` http response, jQuery will reject the promise automatically. – Pablo Feb 04 '18 at 16:25