Asynchronous iterator for-await-of

Asynchronous iterator for-await-of

Iterator interface (iterator)

Collection concepts include strings, arrays, objects, Maps, and Sets. There needs to be a unified interface mechanism to handle all different data structures.

What
Iterator iterator is an interface that provides a unified access mechanism for different data structures.

Benefits

  • Provide a unified and simple access interface for various data structures
  • As long as any data structure deploys the Iterator interface (the value of this interface is an iterator object), it can complete the for..of traversal operation.
  • Enables the members of a data structure to be arranged in a certain order

Usage scenarios

  • for-of traverses, Array.from creates a shallow copy array
  • The spread operator (…) also calls the default Iterator interface.
  • When destructuring and assigning values to arrays and Set structures, the Symbol.iterator method will be called by default.

Principle

Iterator object
Objects that satisfy subordinate conditions can be called iterator objects

  • There is a next method, and each call to the next method will return a result. The result value is an object {value:xxx,done}, value represents the specific return value, and done is of Boolean type, indicating whether the collection The traversal is complete.
  • A pointer is maintained internally to point to the position of the current collection. Each time the next method is called, the pointer will move backward one position.

Deployment iterator interface

  • Iterable data interfaces all have [Symbol.iterator] attributes internally, which is also known as implementing the Iterator interface.
  • The attribute of [Symbol.iterator] will return a function createIterator function, a method of creating an iterator object.
    • There is a method named next in the iterator object
    • The next method will return an object every time it is executed {value: value, done: boolean}

The data structure that deploys the iterator interface is called an iterable object
String, Array, TypedArray, Map and Set all deploy the iterator interface because their prototype objects have a [Symbol.iterator] method.

Case: Create an iterator object

/*
Deploy the iterator interface to the data structure
[Symbol.iterator] = createIterator
*/

// createIterator method: Create an iterator object
// If you need to implement reverse order: i is initialized to items.length-1, followed by i--
function createIterator(items) {<!-- -->
  let i = 0; // internal pointer
  //Iterator object, which has a next method, which returns an object containing two properties: value and done
  return {<!-- -->
    next: function () {<!-- -->
      let done = i >= items.length;
      let val = !done ? items[i + + ] : undefined;
      return {<!-- -->
        done: done,
        value:val
      }
    }
  }
}

//test
var it = createIterator(['a', 'b', 'c']);
console.log(it.next());// {value: "a", done: false}
console.log(it.next());// {value: "b", done: false}
console.log(it.next());// {value: "c", done: false}
console.log(it.next());// "{ value: undefined, done: true }"
console.log(it.next());// "{ value: undefined, done: true }"
console.log(it.next());// "{ value: undefined, done: true }"

Asynchronous iterator

If the loop is a promise array, the value of the promise is obtained asynchronously, so its value cannot be obtained at the current position.

Deploy asynchronous iterator interface
[Symbol.asyncIterator] Asynchronous iterator interface

function createIterator(items) {<!-- -->
  let i = 0; // internal pointer
  //Iterator object, which has a next method, which returns an object containing two properties: value and done
  return {<!-- -->
    next: function () {<!-- -->
      let done = i >= items.length;
      const value = done ? undefined : i + + ;
      return Promise.resolve({<!-- --> value, done });
    }
  }
}

Synchronous iterators and asynchronous iterators

Synchronized iterator

//Iterator object
interface Iterator {<!-- -->
    next(value) : IteratorResult;
    [optional] throw(value) : IteratorResult;
    [optional] return(value) : IteratorResult;
}

//Iterate results
interface IteratorResult {<!-- -->
    value : any;
    done : bool;
}

Asynchronous iterator

//Asynchronous iterator
interface AsyncIterator {<!-- -->
    next(value): Promise<IteratorResult>;
    [optional] throw(value) : Promise<IteratorResult>;
    [optional] return(value) : Promise<IteratorResult>;
}

//Iterate results
interface IteratorResult {<!-- -->
    value : any;
    done : bool;
}

Traversal of asynchronous iterator for-await-of

The for await of loop can pause the loop. When the first asynchronous execution is completed, the next one will be executed. Finally, the output results will be output in synchronous order.

How to obtain the value in the promise array in a loop sequence

For promise arrays, Promise.all() is generally used to obtain the results in order, so that the result of each promise can only be obtained from the last returned array (provided that all promises are successful).
In actual development, assuming that each promise is a request, then perform some operations at the place where the request returns.

So how to obtain the value values in the promise array in a loop order?

//Simulate some data obtained from backend requests, etc.
function Gen(time) {<!-- -->
  return new Promise((resolve) => {<!-- -->
    setTimeout(function () {<!-- -->
      // Simulate that the execution time of each task is different, and the results of task execution are returned by resolve.
      resolve(time);
    }, time);
  });
}

async function test() {<!-- -->
  const arr = [Gen(2000), Gen(1000), Gen(3000)]; //
  let i = 0;
  for (const item of arr) {<!-- -->
    console.log(
       Date.now(),
       await item, //The printing here will be put into the micro queue
    );
  }
}

test();
//1698219031697 2000
//1698219033701 1000
//1698219033701 3000

Although this way of writing can get results, the time is not quite correct. If you imagine the asynchronous task as a back-end request, then when the result of 2000 comes out, 1000 has already been executed. So the final solutionfor-await-of

function Gen(time) {<!-- -->
  return new Promise((resolve, reject) => {<!-- -->
    setTimeout(function () {<!-- -->
      resolve(time)
    }, time)
  })
}

async function test() {<!-- -->
  let arr = [Gen(2000), Gen(1000), Gen(3000)]
  for await(let item of arr) {<!-- -->
    console.log(Date.now(), item) //item is directly the value of promise
  }
}

test()
//1698218308077 2000
//1698218308077 1000
//1698218309080 3000

//Equivalent to
const p1 = await Gen[2000];
console.log(p1);
const p2 = await Gen[1000];
console.log(p2);
const p3 = await Gen[3000];
console.log(p3);