Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at the best features of ES2018.
yield*
in Async Generators
yield*
in async generator works like how they work with normal generators.
If we have:
async function* gen() {
yield 'foo';
yield 'bar';
return 'baz';
}
Then we can use it by writing:
(async function() {
for await (const x of gen()) {
console.log(x);
}
})();
And we get:
foo
bar
logged.
Like with normal generators, the returned value is ignored by the for-of loop.
The operand of yield*
can be any async iterable.
Sync iterables are automatically converted to async ones like with for-await-of.
Async Iteration Errors
In normal generators, next
can throw exceptions.
With async generators, next
can reject promises it returns.
For instance, we can write:
async function* asyncGen() {
throw new Error('error');
}
to throw an error in an async generator.
Then we can catch the error by writing:
asyncGen().next()
.catch(err => console.log(err));
We get the error object logged in the catch
callback.
next
return the rejected promise.
Different Between Async Function and Async Generator Function
An async function returns immediately with a promise.
The promise is fulfilled with return
or rejected with throw
.
So we can either write:
(async function () {
return 'foo';
})()
.then(x => console.log(x));
or:
(async function() {
throw new Error('error');
})()
.catch(x => console.log(x));
Async generators return immediately with an async iterable.
And each call of next
returns a promise with yield x
to fulfill the current promise with {value: x, done: false}
.
It can also throw an error with the current promise with err
.
So we can write:
async function* genFn() {
yield 'foo';
}
const gen = genFn();
gen.next().then(x => console.log(x));
Then x
is {value: “foo”, done: false}
.
Turning an Async Iterable into an Array
We can turn an async variable into an array by looping through the async iterable and then pushing a value into a regular array.
For instance, we can write:
async function convertToArr(asyncIterable) {
const result = [];
const iterator = asyncIterable[Symbol.asyncIterator]();
for await (const v of iterator) {
result.push(v);
}
return result;
}
to create a function that takes an asyncIterable
and then push the items into an array and return it.
It returns a promise that resolved to the result
array.
Then we can use it by writing:
async function* genFn() {
yield 'foo';
yield 'bar';
}
(async () => {
const arr = await convertToArr(genFn());
console.log(arr);
})()
We created an async generator and passes the returned result of it to our convertToArr
function that we created earlier.
Since it returns a promise that resolves to an array, we’ll see the value when we log it.
Internal Properties of Async Generators
Async generators have 2 internal properties.
[[AsyncGeneratorState]]
has the state the generator is currently in.
It can be "suspendedStart"
, "suspendedYield"
, "executing"
, "completed"
or undefined
.
It’s undefined
if it’s not fully initialized.
[[AsyncGeneratorQueue]]
holds the pending invocation of next
, throw
or return
.
Each queue has 2 fields, which are [[Completion]]
and [[Capability]]
.
[[Completion]]
has the parameter for next
, throw
, or return
that leads to the entry to be enqueued.
next
, throw
, or return
indicates the method that created the entry and determines what happens after dequeuing.
[[Capability]]
is the capability of the promise.
Conclusion
yield *
can be used with async generators to call another generator.
There are also big differences between async functions and async generators.