Categories
Modern JavaScript

Best Features of ES2018- Async Iteration

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.

Async Iteration

With ES6, JavaScript introduced the for-of loop.

It lets us synchronously iterate through iterable objects.

However, there’s no way to iterate asynchronously.

ES2018 introduced the async iteration feature to fill this gap.

Synchronous iteration works with any object that has the Symbol.iterator method, which is a generator method.

It returns each item with sequentially when the next next method is called.

The returned object has the value returned from the generator, which is the current entry that the iterator hits.

It also has the done property to indicate whether all th values have been returned.

For instance, if we have:

const iterable = ['foo', 'bar'];
const iterator = iterable[Symbol.iterator]();

Then when we call:

iterator.next()

the first time, we get:

{ value: 'foo', done: false }

Then when we call it again, we get;

{ value: 'b', done: false }

And when we call it one more time, we get:

{ value: undefined, done: true }

The iteration is synchronous, so we can’t iterate asynchronously.

We want to be able to call promises in an iterable object one by one.

This is solved with the for-await-of loop.

For instance, we can use it by writing:

async function main() {
  const arr = [
    Promise.resolve('foo'),
    Promise.resolve('bar'),
  ];
  for await (const x of arr) {
    console.log(x);
  }
}
main();

We have an array of promises that we iterate through.

They’ll be run one by one.

So we get:

foo
bar

logged in the console log.

The for-await-of loop also works with synchronous iterables.

So we can write:

async function main() {
  for await (const x of ['foo', 'bar']) {
    console.log(x);
  }
}
main();

And we get the same results logged.

Async Generators

Async generators let us create async iterable.

An async generator is just a generator function that returns promises.

For instance, we can create an async generator function by writing:

async function* createAsyncIterable(arr) {
  for (const elem of arr) {
    yield elem;
  }
}

A normal generator returns a generator object.

And each invocation of the next method returns an object with the properties value and done .

And an async generator returns a generator object.

Each next call returns a promise for an object with properties value and done .

When we call an async generator, the JavaScript engine will wait for the promise to be settled before calling the next one.

Every async generator has a queue with promises to be settled with yield or throw .

And when next is called, a promise is queued.

And unless the async generator is already running, it resumes and waits for the promise to be finished.

It can finish with yield , throw or return await .

Once it’s finished the promise is returned.

The result of a settled promise is delivered in an async manner.

Conclusion

The for-await-of loop lets us iterate through async generators by returning promises of each value sequentially.

Categories
Modern JavaScript

Best Features of ES2018 — Async Generators

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.

Categories
Modern JavaScript

Best Features of ES2017 — String and Object Methods

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 ES2017.

Object.entries()

Object.entries() is an object static method introduced with ES2017.

It returns an array of key-value pairs of an object with each key-value pair in an array.

For instance, if we have:

const obj = {
  foo: 1,
  bar: 2
};

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

Object.entries takes an object and returns the key-value pairs.

Then we loop through the key-value pairs by destructuring the key-value pair array.

Maps via Object.entries()

Since Object.entries returns an array of key-value pairs in an array and the Map constructor takes that array, we can convert an object into a map with it.

For instance, we can write:

const map = new Map(Object.entries({
  foo: 1,
  bar: 2,
}));
console.log(JSON.stringify([...map]));

Then we get:

[["foo",1],["bar",2]]

Object.values()

Object.values() returns an array of values from the object.

It also takes an object as its argument.

For instance, we can write:

const obj = {
  foo: 1,
  bar: 2
};

Object.values(obj)

Then we get [1, 2] returned.

Object.getOwnPropertyDescriptors()

The Object.getOwnPropertyDescriptors method lets us get the property descriptors of an object.

It only returns the descriptor of non-inherited properties.

For instance, we can write:

const obj = {
  [Symbol('foo')]: 100,
  get bar() {
    return 'abc'
  },
};
console.log(Object.getOwnPropertyDescriptors(obj));

Then we get:

{
  bar: {
    configurable: true
    enumerable: true
  },
  Symbol(foo): {
    configurable: true
    enumerable: true
    value: 100
    writable: true
  }
}

configurable indicates whether we can change the property descriptors.

enumerable indicates whether the property is listed with the for-in loop.

value is the property value.

And writable indicates whether the property value can be changed.

We can use it to copy properties into an object.

If we just copy the value, then we miss all the other property descriptors.

Instead, we can call Object.getOwnPropertyDescriptors to get the object.

So we can write:

const source = {
  foo: 1
};
const target = {};

Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
console.log(Object.getOwnPropertyDescriptor(target, 'foo'));

Then we get all the property descriptors of source copied to target .

And we can set the property descriptors of target.foo to check if everything has been copied over.

String Methods

ES2017 introduced new string method.

New methods include the padStart and padEnd methods.

padStart lets us pad a string to the given name by adding one or more characters repeated to the start of the string.

For instance, if we have:

'x'.padStart(5, 'yz')

then we get:

"yzyzx"

Likewise, there’s a padEnd method that pads a string to a given length with one or more characters repeated to the end of the string.

For instance, we can write:

'x'.padEnd(5, 'yz')

Then we get:

"xyzyz"

returned.

In each method, the first argument is the length, and the 2nd is the string that we want to pad our original string with.

Conclusion

ES2017 has some handy object and string methods we can use.

Categories
Modern JavaScript

Best Features of ES2017 —Async Functions Pitfalls

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 ES2017.

Properties of Async Functions

Async functions don’t wrap promises that we return.

For instance, if we have:

async function asyncFunc() {
  return Promise.resolve('foo');
}

Then if we have:

asyncFunc()
  .then(x => console.log(x))

Then x is 'foo' .

If we return rejected promise, then the thrown error value will be in the catch callback.

For instance, we can write:

async function asyncFunc() {
  return Promise.reject(new Error('error'));
}

Then if we write:

asyncFunc()
  .catch(err => console.error(err));

and error is the Error instance.

Async Function Tips

We should never forget await if our code on the right side of the = is a promise.

For example, we shouldn’t write:

async function asyncFunc() {
  const value = promiseFunc();
  //...
}

That won’t work since there’s no await to wait for the result of the promise.

await makes sense even if the function doesn’t return anything.

We can use the promise a signal to pause the function until something is done.

For instance, we can write:

async function asyncFunc() {
  await sleep(2000);
  //...
}

to make the function pause for 2 seconds with our own sleep function.

We can implement sleep however we like to as long as it returns a promise.

We Don’t Always Need await

If we just want to invoke a promise and don’t want to wait for its result, then we don’t need to add await to it.

For instance, we can write:

async function asyncFunc() {
  const writer = openFile('foo.txt');
  writer.writeLine('foo');
  writer.writeLine('bar');
  await writer.close();
}

We didn’t add await to the writer.writeLine method calls because we don’t want to wait for the result.

This way, we can make our function run faster since we don’t have to wait.

await is sequential, Promise.all() is parallel

We’ve to remember that await is run sequentially.

And Promise.all runs the array of promise we pass in in paralle.

So if we have:

async function foo() {
  const result1 = await promise1();
  const result2 = await promise2();
}

then await waits for each async function is fulfilled until the next line is run.

On the other hand, if we have:

async function foo() {
  const [result1, result2] = await Promise.all([
    promise1(),
    promise2(),
  ]);
}

then promise1 and promise2 are run in parallel.

Async Functions and Callbacks

await only affects the surrounding async function.

So an async function can’t await in a callback.

However, callbacks can be async functions.

This makes callbacks tricky to use.

For instance, we can’t use await in a map callback that isn’t async:

async function download(urls) {
  return urls.map(url => {
    const content = await makeRequest(url);
    return content;
  });
}

We don’t have an async keyword in front of the function.

But we also can’t write:

async function download(urls) {
  return urls.map(async url => {
    const content = await makeRequest(url);
    return content;
  });
}

since we won’t be able to get the results of each async function from the callback.

The awaiting is only done inside the callback.

What we should do is to map our URLs to promises and then call Promise.all on the array of promises.

For instance, we write:

async function download(urls) {
  const promiseArray = urls.map(async (url) => {
    const content = await makeRequest(url);
    return content;
  });
  return await Promise.all(promiseArray);
}

We call map to map the array of URLs to an array of promises, and then we return our Promise.all call to return a promise that resolves to the results of all the promises in an array.

Conclusion

Async functions are handy to use, but it can be misused if we aren’t careful.

So we should make sure to use them properly to avoid unexpected results.

Categories
Modern JavaScript

Best Features of ES2017 — Async Functions and Arrays and Shared Buffers

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 ES2017.

Async Functions and Array.prototype.forEach()

Array.prototype.forEach doesn’t work with the async and await syntax.

For instance, if we have:

async function downloadContent(urls) {
  urls.forEach(async url => {
    const content = await makeRequest(url);
    console.log(content);
  });
}

then we won’t get all the results of the promises because forEach doesn’t wait for each promise to finish.

Instead, we want to use the for-of loop to iterate through each async function to get our result:

async function downloadContent(urls) {
  for (const url of urls) {
    const content = await makeRequest(url);
    console.log(content);
  }
}

The for-of loop is aware of the await operator so we can use it loop run all the async functions sequentially.

If we want to run the async functions in parallel, we can use Promise.all :

async function downloadContent(urls) {
  await Promise.all(urls.map(
    async url => {
      const content = await makeRequest(url);
      console.log(content);
    }));
}

We mapped the URLs to async functions so that we can call Promise.all on the array of promises.

And we return a promise with the resolved value being an array of the promises’ resolved values.

Immediately Invoked Async Function Expressions

We can create async functions that are run immediately.

For instance, instead of writing:

async function foo() {
  console.log(await promiseFunc());
}
foo();

We can write:

(async function () {
  console.log(await promiseFunc());
})();

It can also be an arrow function:

(async () => {
  console.log(await promiseFunc());
})();

Unhandled Rejections

We don’t have to worry about unhandled rejections when we use async functions.

This is because browsers report them to us when we encounter them.

For instance, we can write:

async function foo() {
  throw new Error('error');
}
foo();

Then we’ll see the error logged in the console.

Shared Array Buffers

ES2017 introduced shared array buffers which lets us build concurrent apps.

They let us share the bytes of a SharedArrayBuffer object between multiple workers and the main thread.

The buffer us shared and is wrapped in a typed array so we can access them.

We can share data between workers quickly and coordination between workers is simple and fast.

For instance, we can create a shared array buffer by writing:

const worker = new Worker('worker.js');

const sharedBuffer = new SharedArrayBuffer(
  100 * Int32Array.BYTES_PER_ELEMENT);

worker.postMessage({
  sharedBuffer
});

const sharedArray = new Int32Array(sharedBuffer);

We created a worker in worker.js .

Then we created a shared buffer with the SharedArrayBuffer .

It can contain 100 elements.

Then to share the buffer with the worker, we call postMessage to pass the buffer to the worker,.

To access the buffer’s data, we create a new Int32Array instance.

Then in the worker.js worker, we get the buffer by writing:

self.addEventListener('message', (event) => {
  const {
    sharedBuffer
  } = event.data;
  const sharedArray = new Int32Array(sharedBuffer);
  //...
});

We listen to the message event and get the sharedBuffer property of event.data .

Then we can access it the same way.

Conclusion

Async functions don’t work well with existing array instance methods.

Also, we can use shared array buffers to share data between the main and worker threads.