简体   繁体   中英

Delays between promises in promise chain

Let's say I am using the following code to run a couple of promises in series:

let paramerterArr = ['a','b','c','d','e','f']
parameterArr.reduce(function(promise, item) {
  return promise.then(function(result) {
    return mySpecialFunction(item);
  })
}, Promise.resolve())

The code simply calls mySpecialFunction (which returns a promise), waits for the promise to be resolved and then calls mySpecialFunction again etc. So the function is called once for every element in the array, in the correct order.

How could I make sure that there is a delay of at least 50 milliseconds between every call of mySpecialFunction(item) ?

It is important that the promises are executed in the right order and the execution time of mySpecialFunction varies every time.

I guess a synchronous sleep would work, but I'm not planning to run this code in a separate thread, so it would cause annoying ui freezes in the browser.

I'm not sure if setTimer could somehow be used for this. I mean I can't delay the returning of a promise.

The answers are good, but they wait too long since all the answers wait regardless of whether or not the actual operation took more than 50ms already.

You can use Promise.all for it.

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
let parameterArr = ['a','b','c','d','e','f'];
parameterArr.reduce(function(promise, item) {
  return promise.then(function(result) {
    return Promise.all([delay(50), myPromise(item)]);
  });
}, Promise.resolve());

A really handy utility function to have around is something I call delay() :

function delay(t, val) {
    return new Promise(function(resolve) {
        if (t <= 0) {
            resolve(val);
        } else {
            setTimeout(resolve.bind(null, val), t);
        }
    });
}

Then, you can use it in a promise chain like this:

let paramerterArr = ['a','b','c','d','e','f']
parameterArr.reduce(function(promise, item, index) {
  return promise.then(function(result) {
    // no delay on first iteration
    var delayT = index ? 50 : 0;
    return delay(delayT, item).then(mySpecialFunction);
  })
}, Promise.resolve());

You could also make a little utility function for doing the sequential iteration with optional delay:

// delayT is optional (defaults to 0)
function iterateSerialAsync(array, delayT, fn) {
    if (!fn) {
        fn = delayT;
        delayT = 0;
    }
    array.reduce(function(p, item, index) {
        return p.then(function() {
            // no delay on first iteration
            if (index === 0) delayT = 0;
            return delay(delayT, item).then(fn)
        });
    }, Promise.resolve());
}

And, then you would use it like this:

iterateSerialAsync(paramerterArr, 50, mySpecialFunction).then(function(finalVal) {
    // all done here
});

To get a delay of at least 50ms, use Promise.all :

function delay(t) {
  return new Promise(function(resolve) {
    setTimeout(resolve, t);
  });
}
parameterArr.reduce(function(promise, item) {
  return promise.then(function() {
    return Promise.all([
      mySpecialFunction(item),
      delay(50)
    ]);
  });
}, Promise.resolve());

The following shows an example of how you might achieve a promise that doesn't block but waits for a designated time period:

function timedPromise(ms, payload) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(payload);
        }, ms);
    })
}


var time = Date.now();

timedPromise(1000)
    .then(function() {
        console.log(time - Date.now());
        return timedPromise(2000);
    }).then(function() {
        console.log(time - Date.now());
        return timedPromise(3000);
    });

So, depending on exactly what you want, you should be able to do something like this:

let paramerterArr = ['a','b','c','d','e','f']
parameterArr.reduce(function(promise, item) {
  return promise.then(function(result) {
    return mySpecialFunction(item);
  }).then(function(specialResult) {
    return timedPromise(50, specialResult);
  });
}, Promise.resolve())

Here you go: https://jsbin.com/suvasox/edit?html,js,console

let paramerterArr = ['a','b','c','d','e','f']
paramerterArr.reduce((p, val) => {
  return p.then(() => {
    return new Promise((res) => {
      setTimeout(() => { res(mySpecialFunction(val)); }, 1000); 
    });
  });
}, Promise.resolve());

p has to be the result of the p.then(). Only that way you chain the promises.

Notice, I changed it to 1000ms delay just for emphasis.

since this seems to be a requirement of mySpecialFunction I'd implement it there. So that the function delays itself if it is called less than 50ms after the last call

const delayBetweenCalls = (delay, fn) => {
    let lastCall = NaN;
    return function(/*...arguments*/){
        //this and arguments are both forwarded to fn

        return new Promise(resolve => {
            let poll = () => {
                let delta = Date.now() - lastCall;
                if(delta < delay){
                    setTimeout(poll, delta - delay);
                }else{
                    lastCall = Date.now();
                    resolve( fn.apply(this, arguments) );
                }
            }
            poll();
        })
    }
}

then:

const mySpecialFunction = delayBetweenCalls(50, function(some, ...args){
    return someValueOrPromise;
});

//and your loop stays the same:
parameterArr.reduce(function(promise, item) {
    return promise.then(function(result) {
        return mySpecialFunction(item);
    })
}, Promise.resolve())

so it doesn't matter where/how mySpecialFunction is called, there will always be a delay of at least 50ms before it runs the code inside the passed callback.

Here is my complete solution for delayed promise sequences:


function timeout_sequence_promise(promises = [], timeout = 200) {

    //fake promise used as buffer between promises from params
    const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

    //we need to create array of all promises with delayed buffers
    let delayed_promises = [];

    let total = promises.length;

    let current = 0;

    //every odd promise will be buffer
    while (current < total) {

      delayed_promises.push(promises[current]);
      delayed_promises.push(delay(timeout));

      current++;

    }

    return Promise.all(delayed_promises).then((result) => {

      //we need to filter results from empty odd promises
      return result.filter((item, index) => (index+2)%2 === 0);

    });


  }

It's receive array of promises and timeout delay in ms between them, as parameters.

Hope, it will help you!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM