Home > Software design >  How to use Promise and setTimeout within a generator function in order to create an async generator?
How to use Promise and setTimeout within a generator function in order to create an async generator?

Time:12-06

I'm trying to write a generator function which creates an async generator. The Generator should yield a deferred value at time, each via a promise. The value and the related delay (in milliseconds) come from two different arrays.

As for the provided example code the expected behavior is a logging sequence where, one after the other and waiting for one another, each value from the target array gets logged with its related (same index) delay from the timeArray until the end of the target array's iteration.

I tried using the code shown below but it only prints out the first 2 elements in the timeArray but it doesn't print all of the other elements and I'm not sure if it's printing them after the first two time intervals in timeArray

let j = 0;

// Array that shows how long after each number printed
// to the console the next value should be printed.
var timeArray = [6, 68, 51, 41, 94, 65, 47, 85, 76, 136];

// Array of numbers that should be printed.
var targets = [9, 10, 8, 7, 9, 7, 7, 9, 9, 7];

let generator = generateSequence();

async function* generateSequence(casiInfluence) {
  yield new Promise((resolve, reject) => {
    setTimeout(() => resolve(console.log(targetArray[j]), timeArray[j]); console.log(timeArray[j]);
  });
}
(async function main() {
  for await (var result of generateSequence()) {
    console.log(result);
    j  ;
    result = generator.next();
  }
}());

CodePudding user response:

You created the generator with let generator = generateSequence(), but then you never iterated over it.

(This is spread out and uses more variable names, so it's easier to read):

var timeArray = [6, 68, 51, 41, 94, 65, 47, 85, 76, 136];//Array that shows how long after each number printed to the console the next value should be printed
var targets = [9, 10, 8, 7, 9, 7, 7, 9, 9, 7];//Array of numbers that should be printed

var generator = generateSequence();

( async () => {

    for await ( const printNumber of generator ) {
        /* Accessing the generator's properties logs to the console, so... 
           Nothing to do in this for{} loop. */
    }

} )()

async function* generateSequence() {
    for ( const i in timeArray ) {
        const delay = timeArray[ i ];
        const numberToPrint = targets[ i ];
        await waitForPrint( numberToPrint, delay );
        yield;
    }
}

function waitForPrint( text, delay ) {
    return new Promise( (resolve,reject) => {
        setTimeout(
            () => {
                console.log( text );
                resolve();
            },
            delay
        )
    })
}

This would be easier without generators:


var timeArray = [6, 68, 51, 41, 94, 65, 47, 85, 76, 136];//Array that shows how long after each number printed to the console the next value should be printed
var targets = [9, 10, 8, 7, 9, 7, 7, 9, 9, 7];//Array of numbers that should be printed


for( let scheduledTimeMS=0, i=0; i<timeArray.length; i   ) {

    const numberToPrint = targets[ i ];
    const delay = timeArray[ i ];

    scheduledTimeMS  = delay;

    setTimeout( () => console.log( numberToPrint ), scheduledTimeMS );

}

CodePudding user response:

For each step towards a generator for deferred values the OP could implement an own task ... like ...

  • mapping both of the OP's arrays into a better processable data-structure ... here an array of tuples where each tuple is made of ... [<value>, <delay>].

  • mapping of each value-delay tuple into a deferred value action which is an async function that creates and returns a promise which will resolve the value after the milliseconds delay where the mapping function that creates the async function is called createDeferredValue.

  • creating the async generator from the array of async functions where the generator function is called createPoolOfDeferredValues.

// - a specific helper which creates an async function
//   (encapsulating a promise) for each to be deferred value.
function createDeferredValue(value, delay) {
  return async function () {
    return await (
      new Promise(resolve => setTimeout(resolve, delay, value))
    );
  };
}

// - another helper which creates an async generator
//   from an array of async functions.
async function* createPoolOfDeferredValues(deferredValueList) {
  const asyncFunctions = [...deferredValueList];

  let asyncFct;
  while (asyncFct = asyncFunctions.shift()) {

    yield (await asyncFct());
  }
}


// - array that defines how long after each to be
//   processed value the next value will be processed.
const valueDelays = [600, 680, 510, 410, 940, 650, 470, 850, 760, 1360];

// - array of to be processed target values.
const targetValues = [9, 10, 8, 7, 9, 7, 7, 9, 9, 7]


// - maps both above OP's arrays into a
//   better processable data-structure.
const targetEntries = targetValues
  .map((value, idx) => [value, valueDelays[idx]]);

console.log({ targetEntries });

// - helper task which creates a list of async functions.
const deferredValueList = targetEntries
  .map(([value, delay]) =>
    createDeferredValue(value, delay)
  );

// create an async generator ...
const poolOfDeferredValues =
  createPoolOfDeferredValues(deferredValueList);

(async () => {
  // ... and iterate over it.
  for await (const value of poolOfDeferredValues) {
    console.log({ value });
  }
})();

console.log('... running ...');
.as-console-wrapper { min-height: 100%!important; top: 0; }

But of cause, once having created the list/array of async functions, one can iterate it directly with for...await without the detour of an async generator. Thus the above provided code simplifies to ...

// - a specific helper which creates an async function
//   (encapsulating a promise) for each to be deferred value.
function createDeferredValue(value, delay) {
  return async function () {
    return await (
      new Promise(resolve => setTimeout(resolve, delay, value))
    );
  };
}

// - array that defines how long after each to be
//   processed value the next value will be processed.
const valueDelays = [600, 680, 510, 410, 940, 650, 470, 850, 760, 1360];

// - array of to be processed target values.
const targetValues = [9, 10, 8, 7, 9, 7, 7, 9, 9, 7]


// - maps both above OP's arrays into a
//   better processable data-structure.
const targetEntries = targetValues
  .map((value, idx) => [value, valueDelays[idx]]);

console.log({ targetEntries });

// - helper task which creates a list of async functions.
const deferredValueList = targetEntries
  .map(([value, delay]) =>
    createDeferredValue(value, delay)
  );

(async () => {
  // - iterate the list of async functions by `for...await`.
  for await (const deferredValue of deferredValueList) {
    const value = await deferredValue();
    console.log({ value });
  }
})();

console.log('... running ...');
.as-console-wrapper { min-height: 100%!important; top: 0; }

  • Related