Tag: Should

7 things you should know when getting started with Serverless APIs

I want you to take a second and think about Twitter, and think about it in terms of scale. Twitter has 326 million users. Collectively, we create ~6,000 tweets every second. Every minute, that’s 360,000 tweets created. That sums up to nearly 200 billion tweets a year. Now, what if the creators of Twitter had been paralyzed by how to scale and they didn’t even begin?

That’s me on every single startup idea I’ve ever had, which is why I love serverless so much: it handles the issues of scaling leaving me to build the next Twitter!

Live metrics with Application Insights

As you can see in the above, we scaled from one to seven servers in a matter of seconds, as more user requests come in. You can scale that easily, too.

So let’s build an API that will scale instantly as more and more users come in and our workload increases. We’re going to do that is by answering the following questions:


How do I create a new serverless project?

With every new technology, we need to figure out what tools are available for us and how we can integrate them into our existing tool set. When getting started with serverless, we have a few options to consider.

First, we can use the good old browser to create, write and test functions. It’s powerful, and it enables us to code wherever we are; all we need is a computer and a browser running. The browser is a good starting point for writing our very first serverless function.

Serverless in the browser

Next, as you get more accustomed to the new concepts and become more productive, you might want to use your local environment to continue with your development. Typically you’ll want support for a few things:

  • Writing code in your editor of choice
  • Tools that do the heavy lifting and generate the boilerplate code for you
  • Run and debug code locally
  • Support for quickly deploying your code

Microsoft is my employer and I’ve mostly built serverless applications using Azure Functions so for the rest of this article I’ll continue using them as an example. With Azure Functions, you’ll have support for all these features when working with the Azure Functions Core Tools which you can install from npm.

npm install -g azure-functions-core-tools

Next, we can initialize a new project and create new functions using the interactive CLI:

func CLI

If your editor of choice happens to be VS Code, then you can use it to write serverless code too. There’s actually a great extension for it.

Once installed, a new icon will be added to the left-hand sidebar — this is where we can access all our Azure-related extensions! All related functions can to be grouped under the same project (also known as a function app). This is like a folder for grouping functions that should scale together and that we want to manage and monitor at the same time. To initialize a new project using VS Code, click on the Azure icon and then the folder icon.

Create new Azure Functions project

This will generate a few files that help us with global settings. Let’s go over those now.

host.json

We can configure global options for all functions in the project directly in the host.json file.

In it, our function app is configured to use the latest version of the serverless runtime (currently 2.0). We also configure functions to timeout after ten minutes by setting the functionTimeout property to 00:10:00 — the default value for that is currently five minutes (00:05:00).

In some cases, we might want to control the route prefix for our URLs or even tweak settings, like the number of concurrent requests. Azure Functions even allows us to customize other features like logging, healthMonitor and different types of extensions.

Here’s an example of how I’ve configured the file:

// host.json {   "version": "2.0",   "functionTimeout": "00:10:00",   "extensions": {   "http": {     "routePrefix": "tacos",     "maxOutstandingRequests": 200,     "maxConcurrentRequests": 100,     "dynamicThrottlesEnabled": true   } } }

Application settings

Application settings are global settings for managing runtime, language and version, connection strings, read/write access, and ZIP deployment, among others. Some are settings that are required by the platform, like FUNCTIONS_WORKER_RUNTIME, but we can also define custom settings that we’ll use in our application code, like DB_CONN which we can use to connect to a database instance.

While developing locally, we define these settings in a file named local.settings.json and we access them like any other environment variable.

Again, here’s an example snippet that connects these points:

// local.settings.json {   "IsEncrypted": false,   "Values": {     "AzureWebJobsStorage": "your_key_here",     "FUNCTIONS_WORKER_RUNTIME": "node",     "WEBSITE_NODE_DEFAULT_VERSION": "8.11.1",     "FUNCTIONS_EXTENSION_VERSION": "~2",     "APPINSIGHTS_INSTRUMENTATIONKEY": "your_key_here",     "DB_CONN": "your_key_here",   } }

Azure Functions Proxies

Azure Functions Proxies are implemented in the proxies.json file, and they enable us to expose multiple function apps under the same API, as well as modify requests and responses. In the code below we’re publishing two different endpoints under the same URL.

// proxies.json {   "$ schema": "http://json.schemastore.org/proxies",   "proxies": {     "read-recipes": {         "matchCondition": {         "methods": ["POST"],         "route": "/api/recipes"       },       "backendUri": "https://tacofancy.azurewebsites.net/api/recipes"     },     "subscribe": {       "matchCondition": {         "methods": ["POST"],         "route": "/api/subscribe"       },       "backendUri": "https://tacofancy-users.azurewebsites.net/api/subscriptions"     }   } }

Create a new function by clicking the thunder icon in the extension.

Create a new Azure Function

The extension will use predefined templates to generate code, based on the selections we made — language, function type, and authorization level.

We use function.json to configure what type of events our function listens to and optionally to bind to specific data sources. Our code runs in response to specific triggers which can be of type HTTP when we react to HTTP requests — when we run code in response to a file being uploaded to a storage account. Other commonly used triggers can be of type queue, to process a message uploaded on a queue or time triggers to run code at specified time intervals. Function bindings are used to read and write data to data sources or services like databases or send emails.

Here, we can see that our function is listening to HTTP requests and we get access to the actual request through the object named req.

// function.json {   "disabled": false,   "bindings": [     {       "authLevel": "anonymous",       "type": "httpTrigger",       "direction": "in",       "name": "req",       "methods": ["get"],       "route": "recipes"     },     {       "type": "http",       "direction": "out",       "name": "res"     }   ] }

index.js is where we implement the code for our function. We have access to the context object, which we use to communicate to the serverless runtime. We can do things like log information, set the response for our function as well as read and write data from the bindings object. Sometimes, our function app will have multiple functions that depend on the same code (i.e. database connections) and it’s good practice to extract that code into a separate file to reduce code duplication.

//Index.js module.exports = async function (context, req) {   context.log('JavaScript HTTP trigger function processed a request.');    if (req.query.name || (req.body && req.body.name)) {       context.res = {           // status: 200, /* Defaults to 200 */           body: "Hello " + (req.query.name || req.body.name)       };   }   else {       context.res = {           status: 400,           body: "Please pass a name on the query string or in the request body"       };   } };

Who’s excited to give this a run?

How do I run and debug Serverless functions locally?

When using VS Code, the Azure Functions extension gives us a lot of the setup that we need to run and debug serverless functions locally. When we created a new project using it, a .vscode folder was automatically created for us, and this is where all the debugging configuration is contained. To debug our new function, we can use the Command Palette (Ctrl+Shift+P) by filtering on Debug: Select and Start Debugging, or typing debug.

Debugging Serverless Functions

One of the reasons why this is possible is because the Azure Functions runtime is open-source and installed locally on our machine when installing the azure-core-tools package.

How do I install dependencies?

Chances are you already know the answer to this, if you’ve worked with Node.js. Like in any other Node.js project, we first need to create a package.json file in the root folder of the project. That can done by running npm init -y — the -y will initialize the file with default configuration.

Then we install dependencies using npm as we would normally do in any other project. For this project, let’s go ahead and install the MongoDB package from npm by running:

npm i mongodb

The package will now be available to import in all the functions in the function app.

How do I connect to third-party services?

Serverless functions are quite powerful, enabling us to write custom code that reacts to events. But code on its own doesn’t help much when building complex applications. The real power comes from easy integration with third-party services and tools.

So, how do we connect and read data from a database? Using the MongoDB client, we’ll read data from an Azure Cosmos DB instance I have created in Azure, but you can do this with any other MongoDB database.

//Index.js const MongoClient = require('mongodb').MongoClient;  // Initialize authentication details required for database connection const auth = {   user: process.env.user,   password: process.env.password };  // Initialize global variable to store database connection for reuse in future calls let db = null; const loadDB = async () => {   // If database client exists, reuse it     if (db) {     return db;   }   // Otherwise, create new connection     const client = await MongoClient.connect(     process.env.url,     {       auth: auth     }   );   // Select tacos database   db = client.db('tacos');   return db; };  module.exports = async function(context, req) {   try {     // Get database connection     const database = await loadDB();     // Retrieve all items in the Recipes collection      let recipes = await database       .collection('Recipes')       .find()       .toArray();     // Return a JSON object with the array of recipes      context.res = {       body: { items: recipes }     };   } catch (error) {     context.log(`Error code: $ {error.code} message: $ {error.message}`);     // Return an error message and Internal Server Error status code     context.res = {       status: 500,       body: { message: 'An error has occurred, please try again later.' }     };   } };

One thing to note here is that we’re reusing our database connection rather than creating a new one for each subsequent call to our function. This shaves off ~300ms of every subsequent function call. I call that a win!

Where can I save connection strings?

When developing locally, we can store our environment variables, connection strings, and really anything that’s secret into the local.settings.json file, then access it all in the usual manner, using process.env.yourVariableName.

local.settings.json {   "IsEncrypted": false,   "Values": {     "AzureWebJobsStorage": "",     "FUNCTIONS_WORKER_RUNTIME": "node",     "user": "your-db-user",     "password": "your-db-password",     "url": "mongodb://your-db-user.documents.azure.com:10255/?ssl=true"   } }

In production, we can configure the application settings on the function’s page in the Azure portal.

However, another neat way to do this is through the VS Code extension. Without leaving your IDE, we can add new settings, delete existing ones or upload/download them to the cloud.

Debugging Serverless Functions

How do I customize the URL path?

With the REST API, there are a couple of best practices around the format of the URL itself. The one I settled on for our Recipes API is:

  • GET /recipes: Retrieves a list of recipes
  • GET /recipes/1: Retrieves a specific recipe
  • POST /recipes: Creates a new recipe
  • PUT /recipes/1: Updates recipe with ID 1
  • DELETE /recipes/1: Deletes recipe with ID 1

The URL that is made available by default when creating a new function is of the form http://host:port/api/function-name. To customize the URL path and the method that we listen to, we need to configure them in our function.json file:

// function.json {   "disabled": false,   "bindings": [     {       "authLevel": "anonymous",       "type": "httpTrigger",       "direction": "in",       "name": "req",       "methods": ["get"],       "route": "recipes"     },     {       "type": "http",       "direction": "out",       "name": "res"     }   ] }

Moreover, we can add parameters to our function’s route by using curly braces: route: recipes/{id}. We can then read the ID parameter in our code from the req object:

const recipeId = req.params.id;

How can I deploy to the cloud?

Congratulations, you’ve made it to the last step! 🎉 Time to push this goodness to the cloud. As always, the VS Code extension has your back. All it really takes is a single right-click we’re pretty much done.

Deployment using VS Code

The extension will ZIP up the code with the Node modules and push them all to the cloud.

While this option is great when testing our own code or maybe when working on a small project, it’s easy to overwrite someone else’s changes by accident — or even worse, your own.

Don’t let friends right-click deploy!
— every DevOps engineer out there

A much healthier option is setting up on GitHub deployment which can be done in a couple of steps in the Azure portal, via the Deployment Center tab.

Github deployment

Are you ready to make Serverless APIs?

This has been a thorough introduction to the world of Servless APIs. However, there’s much, much more than what we’ve covered here. Serverless enables us to solve problems creatively and at a fraction of the cost we usually pay for using traditional platforms.

Chris has mentioned it in other posts here on CSS-Tricks, but he created this excellent website where you can learn more about serverless and find both ideas and resources for things you can build with it. Definitely check it out and let me know if you have other tips or advice scaling with serverless.

The post 7 things you should know when getting started with Serverless APIs appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,

Should I Use Source Maps in Production?

It’s a valid question. A “source map” is a special file that connects a minified/uglified version of an asset (CSS or JavaScript) to the original authored version. Say you’ve got a filed called _header.scss that gets imported into global.scss which is compiled to global.css. That final CSS file is what gets loaded in the browser, so for example, when you inspect an element in DevTools, it might tell you that the <nav> is display: flex; because it says so on line 387 in global.css.

On line 528 of page.css</, we can find out that <code>.meta has position: relative;

But because that final CSS file is probably minified (all whitespace removed), DevTools is likely to tell us that we’ll find the declaration we’re looking for on line 1! Unfortunate, and not helpful for development.

That’s where source maps come in. Like I said up top, source maps are special files that connect that final output file the browser is actually using with the authored files that you actually work with and write code in on your file system.

Typically, source maps are a configuration option from the preprocessor. Here’s Babel’s options. I believe that with Sass, you don’t even have to pass a flag for it in the command or anything because it produces source maps by default.

So, these source maps are for developers. They are particularly useful for you and your team because they help tremendously for debugging issues as well as day-to-day work. I’m sure I make use of them just about every day. I’d say in general, they are used for local development. You might even .gitignore them or skip them in a deployment process in order to serve and store fewer assets to production. But there’s been some recent chatter about making sure they go to production as well.

David Heinemeier Hansson:

But source maps have long been seen merely as a local development tool. Not something you ship to production, although people have also been doing that, such that live debugging would be easier. That in itself is a great reason to ship source maps. […]

Additional, Rails 6 just committed to shipping source maps by default in production, also thanks to Webpack. You’ll be able to turn that feature off, but I hope you won’t. The web is a better place when we allow others to learn from our work.

Check out that issue thread for more interesting conversation about shipping source maps to production. The benefits boil down to these two things:

  1. It might help you track down bugs in production more easily
  2. It helps other people learn from your website more easily

Both are cool. Personally, I’d be opposed to shipping performance-optimized code for learning purposes alone. I wrote about that last year:

I don’t want my source to be human-readable, not for protective reasons, but because I care about web performance more. I want my website to arrive at light speed on a tiny spec of magical network packet dust and blossom into a complete website. Or do whatever computer science deems is the absolute fastest way to send website data between computers. I’m much more worried about the state of web performance than I am about web education. But even if I was very worried about web education, I don’t think it’s the network’s job to deliver teachability.

Shipping source maps to production is a nice middle ground. There’s no hit on performance (source maps don’t get loaded unless you have DevTools open, which is, IMO, irrelevant to a real performance discussion) with the benefit of delivering debugging and learning benefits.

The downsides brought up in recent discussion boil down to:

  1. Sourcemaps require compilation time
  2. It allows people to, I dunno, steal your code or something

I don’t care about #2 (sorry), and #1 seems generally negligible for a small or what we think of as the average site, though I’m afraid I can’t speak for mega sites.

One thing I should add though is that source maps can even be generated for CSS-in-JS tooling, so for those that literally inject styles into the DOM for you, those source maps are injected as well. I’ve seen major slowdowns in those situations, so I would say definitely do not ship source maps to production if you can’t split them out of your main bundles. Otherwise, I’d vote strongly that you do.

The post Should I Use Source Maps in Production? appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

New ES2018 Features Every JavaScript Developer Should Know

The ninth edition of the ECMAScript standard, officially known as ECMAScript 2018 (or ES2018 for short), was released in June 2018. Starting with ES2016, new versions of ECMAScript specifications are released yearly rather than every several years and add fewer features than major editions used to. The newest edition of the standard continues the yearly release cycle by adding four new RegExp features, rest/spread properties, asynchronous iteration, and Promise.prototype.finally. Additionally, ES2018 drops the syntax restriction of escape sequences from tagged templates.

These new changes are explained in the subsections that follow.

The Rest/Spread Properties

One of the most interesting features added to ES2015 was the spread operator. This operator makes copying and merging arrays a lot simpler. Rather than calling the concat() or slice() method, you could use the ... operator:

const arr1 = [10, 20, 30];  // make a copy of arr1 const copy = [...arr1];  console.log(copy);    // → [10, 20, 30]  const arr2 = [40, 50];  // merge arr2 with arr1 const merge = [...arr1, ...arr2];  console.log(merge);    // → [10, 20, 30, 40, 50]

The spread operator also comes in handy in situations where an array must be passed in as separate arguments to a function. For example:

const arr = [10, 20, 30]  // equivalent to // console.log(Math.max(10, 20, 30)); console.log(Math.max(...arr));    // → 30

ES2018 further expands this syntax by adding spread properties to object literals. With the spread properties you can copy own enumerable properties of an object onto a new object. Consider the following example:

const obj1 = {   a: 10,   b: 20 };  const obj2 = {   ...obj1,   c: 30 };  console.log(obj2);    // → {a: 10, b: 20, c: 30}

In this code, the ... operator is used to retrieve the properties of obj1 and assign them to obj2. Prior to ES2018, attempting to do so would throw an error. If there are multiple properties with the same name, the property that comes last will be used:

const obj1 = {   a: 10,   b: 20 };  const obj2 = {   ...obj1,   a: 30 };  console.log(obj2);    // → {a: 30, b: 20}

Spread properties also provide a new way to merge two or more objects, which can be used as an alternative to the Object.assign() method:

const obj1 = {a: 10}; const obj2 = {b: 20}; const obj3 = {c: 30};  // ES2018 console.log({...obj1, ...obj2, ...obj3});    // → {a: 10, b: 20, c: 30}  // ES2015 console.log(Object.assign({}, obj1, obj2, obj3));    // → {a: 10, b: 20, c: 30}

Note, however, that spread properties do not always produce the same result as Object.assign(). Consider the following code:

Object.defineProperty(Object.prototype, 'a', {   set(value) {     console.log('set called!');   } });  const obj = {a: 10};  console.log({...obj});     // → {a: 10}  console.log(Object.assign({}, obj));     // → set called! // → {}

In this code, the Object.assign() method executes the inherited setter property. Conversely, the spread properties simply ignore the setter.

It’s important to remember that spread properties only copy enumerable properties. In the following example, the type property won’t show up in the copied object because its enumerable attribute is set to false:

const car = {   color: 'blue' };  Object.defineProperty(car, 'type', {   value: 'coupe',   enumerable: false });  console.log({...car});    // → {color: "blue"}

Inherited properties are ignored even if they are enumerable:

const car = {   color: 'blue' };  const car2 = Object.create(car, {   type: {     value: 'coupe',     enumerable: true,   } });  console.log(car2.color);                      // → blue console.log(car2.hasOwnProperty('color'));    // → false  console.log(car2.type);                       // → coupe console.log(car2.hasOwnProperty('type'));     // → true  console.log({...car2});                       // → {type: "coupe"}

In this code, car2 inherits the color property from car. Because spread properties only copy the own properties of an object, color is not included in the return value.

Keep in mind that spread properties can only make a shallow copy of an object. If a property holds an object, only the reference to the object will be copied:

const obj = {x: {y: 10}}; const copy1 = {...obj};     const copy2 = {...obj};   console.log(copy1.x === copy2.x);    // → true

The x property in copy1 refers to the same object in memory that x in copy2 refers to, so the strict equality operator returns true.

Another useful feature added to ES2015 was rest parameters, which enabled JavaScript programmers to use ... to represent values as an array. For example:

const arr = [10, 20, 30]; const [x, ...rest] = arr;  console.log(x);       // → 10 console.log(rest);    // → [20, 30]

Here, the first item in arr is assigned to x, and remaining elements are assigned to the rest variable. This pattern, called array destructuring, became so popular that the Ecma Technical Committee decided to bring a similar functionality to objects:

const obj = {   a: 10,   b: 20,   c: 30 };  const {a, ...rest} = obj;  console.log(a);       // → 10 console.log(rest);    // → {b: 20, c: 30}

This code uses the rest properties in a destructuring assignment to copy the remaining own enumerable properties into a new object. Note that rest properties must always appear at the end of the object, otherwise an error is thrown:

const obj = {   a: 10,   b: 20,   c: 30 };  const {...rest, a} = obj;    // → SyntaxError: Rest element must be last element

Also keep in mind that using multiple rest syntaxes in an object causes an error, unless they are nested:

const obj = {   a: 10,   b: {     x: 20,     y: 30,     z: 40   } };  const {b: {x, ...rest1}, ...rest2} = obj;    // no error  const {...rest, ...rest2} = obj;    // → SyntaxError: Rest element must be last element

Support for Rest/Spread Properties

Chrome Firefox Safari Edge
60 55 11.1 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
60 55 11.3 No 8.2 60

Node.js:

  • 8.0.0 (requires the --harmony runtime flag)
  • 8.3.0 (full support)

Asynchronous Iteration

Iterating over a collection of data is an important part of programming. Prior to ES2015, JavaScript provided statements such as for, for...in, and while, and methods such as map(), filter(), and forEach() for this purpose. To enable programmers to process the elements in a collection one at a time, ES2015 introduced the iterator interface.

An object is iterable if it has a Symbol.iterator property. In ES2015, strings and collections objects such as Set, Map, and Array come with a Symbol.iterator property and thus are iterable. The following code gives an example of how to access the elements of an iterable one at a time:

const arr = [10, 20, 30]; const iterator = arr[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

Symbol.iterator is a well-known symbol specifying a function that returns an iterator. The primary way to interact with an iterator is the next() method. This method returns an object with two properties: value and done. The value property contains the value of the next element in the collection. The done property contains either true or false denoting whether or not the end of the collection has reached.

By default, a plain object is not iterable, but it can become iterable if you define a Symbol.iterator property on it, as in this example:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.iterator]() {     const values = Object.keys(this);     let i = 0;     return {       next: () => {         return {           value: this[values[i++]],           done: i > values.length         }       }     };   } };  const iterator = collection[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

This object is iterable because it defines a Symbol.iterator property. The iterator uses the Object.keys() method to get an array of the object’s property names and then assigns it to the values constant. It also defines a counter variable and gives it an initial value of 0. When the iterator is executed it returns an object that contains a next() method. Each time the next() method is called, it returns a {value, done} pair, with value holding the next element in the collection and done holding a Boolean indicating if the iterator has reached the need of the collection.

While this code works perfectly, it’s unnecessarily complicated. Fortunately, using a generator function can considerably simplify the process:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.iterator]: function * () {     for (let key in this) {       yield this[key];     }   } };  const iterator = collection[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

Inside this generator, a for...in loop is used to enumerate over the collection and yield the value of each property. The result is exactly the same as the previous example, but it’s greatly shorter.

A downside of iterators is that they are not suitable for representing asynchronous data sources. ES2018’s solution to remedy that is asynchronous iterators and asynchronous iterables. An asynchronous iterator differs from a conventional iterator in that, instead of returning a plain object in the form of {value, done}, it returns a promise that fulfills to {value, done}. An asynchronous iterable defines a Symbol.asyncIterator method (instead of Symbol.iterator) that returns an asynchronous iterator.

An example should make this clearer:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]() {     const values = Object.keys(this);     let i = 0;     return {       next: () => {         return Promise.resolve({           value: this[values[i++]],            done: i > values.length         });       }     };   } };  const iterator = collection[Symbol.asyncIterator]();    console.log(iterator.next().then(result => {   console.log(result);    // → {value: 10, done: false} }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 20, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 30, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: undefined, done: true}  }));

Note that it’s not possible to use an iterator of promises to achieve the same result. Although a normal, synchronous iterator can asynchronously determine the values, it still needs to determine the state of “done” synchronously.

Again, you can simplify the process by using a generator function, as shown below:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]: async function * () {     for (let key in this) {       yield this[key];     }   } };  const iterator = collection[Symbol.asyncIterator]();    console.log(iterator.next().then(result => {   console.log(result);    // → {value: 10, done: false} }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 20, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 30, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: undefined, done: true}  }));

Normally, a generator function returns a generator object with a next() method. When next() is called it returns a {value, done} pair whose value property holds the yielded value. An async generator does the same thing except that it returns a promise that fulfills to {value, done}.

An easy way to iterate over an iterable object is to use the for...of statement, but for...of doesn’t work with async iterables as value and done are not determined synchronously. For this reason, ES2018 provides the for...await...of statement. Let’s look at an example:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]: async function * () {     for (let key in this) {       yield this[key];     }   } };  (async function () {   for await (const x of collection) {     console.log(x);   } })();  // logs: // → 10 // → 20 // → 30

In this code, the for...await...of statement implicitly calls the Symbol.asyncIterator method on the collection object to get an async iterator. Each time through the loop, the next() method of the iterator is called, which returns a promise. Once the promise is resolved, the value property of the resulting object is read to the x variable. The loop continues until the done property of the returned object has a value of true.

Keep in mind that the for...await...of statement is only valid within async generators and async functions. Violating this rule results in a SyntaxError.

The next() method may return a promise that rejects. To gracefully handle a rejected promise, you can wrap the for...await...of statement in a try...catch statement, like this:

const collection = {   [Symbol.asyncIterator]() {     return {       next: () => {         return Promise.reject(new Error('Something went wrong.'))       }     };   } };  (async function() {   try {     for await (const value of collection) {}   } catch (error) {     console.log('Caught: ' + error.message);   } })();  // logs: // → Caught: Something went wrong.

Support for Asynchronous Iterators

Chrome Firefox Safari Edge
63 57 12 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 57 12 No 8.2 63

Node.js:

  • 8.10.0 (requires the –harmony_async_iteration flag)
  • 10.0.0 (full support)

Promise.prototype.finally

Another exciting addition to ES2018 is the finally() method. Several JavaScript libraries had previously implemented a similar method, which proved useful in many situations. This encouraged the Ecma Technical Committee to officially add finally() to the specification. With this method, programmers will be able to execute a block of code regardless of the promise’s fate. Let’s look at a simple example:

fetch('https://www.google.com')   .then((response) => {     console.log(response.status);   })   .catch((error) => {      console.log(error);   })   .finally(() => {      document.querySelector('#spinner').style.display = 'none';   });

The finally() method comes in handy when you need to do some clean up after the operation has finished regardless of whether or not it succeeded. In this code, the finally() method simply hides the loading spinner after the data is fetched and processed. Instead of duplicating the final logic in the then() and catch() methods, the code registers a function to be executed once the promise is either fulfilled or rejected.

You could achieve the same result by using promise.then(func, func) rather than promise.finally(func), but you would have to repeat the same code in both fulfillment handler and rejection handler, or declare a variable for it:

fetch('https://www.google.com')   .then((response) => {     console.log(response.status);   })   .catch((error) => {      console.log(error);   })   .then(final, final);  function final() {   document.querySelector('#spinner').style.display = 'none'; }

As with then() and catch(), the finally() method always returns a promise, so you can chain more methods. Normally, you want to use finally() as the last chain, but in certain situations, such as when making a HTTP request, it’s a good practice to chain another catch() to deal with errors that may occur in finally().

Support for Promise.prototype.finally

Chrome Firefox Safari Edge
63 58 11.1 18
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 58 11.1 No 8.2 63

Node.js:

10.0.0 (full support)

New RegExp Features

ES2018 adds four new features to the RegExp object, which further improves JavaScript’s string processing capabilities. These features are as follows:

  • s (dotAll) flag
  • Named capture groups
  • Lookbehind assertions
  • Unicode property escapes

s (dotAll) Flag

The dot (.) is a special character in a regular expression pattern that matches any character except line break characters such as line feed (\n) or carriage return (\r). A workaround to match all characters including line breaks is to use a character class with two opposite shorthands such as [\d\D]. This character class tells the regular expression engine to find a character that’s either a digit (\d) or a non-digit (\D). As a result, it matches any character:

console.log(/one[\d\D]two/.test('one\ntwo'));    // → true

ES2018 introduces a mode in which the dot can be used to achieve the same result. This mode can be activated on per-regex basis by using the s flag:

console.log(/one.two/.test('one\ntwo'));     // → false console.log(/one.two/s.test('one\ntwo'));    // → true

The benefit of using a flag to opt in to the new behavior is backwards compatibility. So existing regular expression patterns that use the dot character are not affected.

Named Capture Groups

In some regular expression patterns, using a number to reference a capture group can be confusing. For example, take the regular expression /(\d{4})-(\d{2})-(\d{2})/ which matches a date. Because date notation in American English is different from British English, it’s hard to know which group refers to the day and which group refers to the month:

const re = /(\d{4})-(\d{2})-(\d{2})/; const match= re.exec('2019-01-10');  console.log(match[0]);    // → 2019-01-10 console.log(match[1]);    // → 2019 console.log(match[2]);    // → 01 console.log(match[3]);    // → 10

ES2018 introduces named capture groups which uses the (?<name>...) syntax. So, the pattern to match a date can be written in a less ambiguous manner:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2019-01-10');  console.log(match.groups);          // → {year: "2019", month: "01", day: "10"} console.log(match.groups.year);     // → 2019 console.log(match.groups.month);    // → 01 console.log(match.groups.day);      // → 10

You can recall a named capture group later in the pattern by using the \k<name> syntax. For example, to find consecutive duplicate words in a sentence, you can use /\b(?<dup>\w+)\s+\k<dup>\b/:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/; const match = re.exec('Get that that cat off the table!');          console.log(match.index);    // → 4 console.log(match[0]);       // → that that

To insert a named capture group into the replacement string of the replace() method, you will need to use the $ <name> construct. For example:

const str = 'red & blue';  console.log(str.replace(/(red) & (blue)/, '$  2 & $  1'));     // → blue & red  console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$  <blue> & $  <red>'));     // → blue & red

Lookbehind Assertions

ES2018 brings lookbehind assertions to JavaScript, which have been available in other regex implementations for years. Previously, JavaScript only supported lookahead assertions. A lookbehind assertion is denoted by (?<=...), and enables you to match a pattern based on the substring that precedes the pattern. For example, if you want to match the price of a product in dollar, pound, or euro without capturing the currency symbol, you can use /(?<=$ |£|€)\d+(\.\d*)?/:

const re = /(?<=$  |£|€)\d+(\.\d*)?/;  console.log(re.exec('199'));      // → null  console.log(re.exec('$  199'));     // → ["199", undefined, index: 1, input: "$  199", groups: undefined]  console.log(re.exec('€50'));      // → ["50", undefined, index: 1, input: "€50", groups: undefined]

There is also a negative version of lookbehind, which is denoted by (?<!...). A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(?<!un)available/ matches the word available if it does not have a “un” prefix:

const re = /(?<!un)available/;  console.log(re.exec('We regret this service is currently unavailable'));     // → null  console.log(re.exec('The service is available'));              // → ["available", index: 15, input: "The service is available", groups: undefined]

Unicode Property Escapes

ES2018 provides a new type of escape sequence known as Unicode property escape, which provides support for full Unicode in regular expressions. Suppose you want to match the Unicode character ㉛ in a string. Although ㉛ is considered a number, you can’t match it with the \d shorthand character class because it only supports ASCII [0-9] characters. Unicode property escapes, on the other hand, can be used to match any decimal number in Unicode:

const str = '㉛';  console.log(/\d/u.test(str));    // → false console.log(/\p{Number}/u.test(str));     // → true

Similarly, if you want to match any Unicode word character, you can use \p{Alphabetic}:

const str = 'ض';  console.log(/\p{Alphabetic}/u.test(str));     // → true  // the \w shorthand cannot match ض   console.log(/\w/u.test(str));    // → false

There is also a negated version of \p{...}, which is denoted by \P{...}:

console.log(/\P{Number}/u.test('㉛'));    // → false console.log(/\P{Number}/u.test('ض'));    // → true  console.log(/\P{Alphabetic}/u.test('㉛'));    // → true console.log(/\P{Alphabetic}/u.test('ض'));    // → false

In addition to Alphabetic and Number, there are several more properties that can be used in Unicode property escapes. You can find a list of supported Unicode properties in the current specification proposal.

Support for New RegExp Features

Chrome Firefox Safari Edge
s (dotAll) Flag 62 No 11.1 No
Named Capture Groups 64 No 11.1 No
Lookbehind Assertions 62 No No No
Unicode Property Escapes 64 No 11.1 No
Chrome (Android) Firefox (Android) iOS Safari Edge Mobile Samsung Internet Android Webview
s (dotAll) Flag 62 No 11.3 No 8.2 62
Named Capture Groups 64 No 11.3 No No 64
Lookbehind Assertions 62 No No No 8.2 62
Unicode Property Escapes 64 No 11.3 No No 64

Node.js:

  • 8.3.0 (requires the –harmony runtime flag)
  • 8.10.0 (support for s (dotAll) flag and lookbehind assertions)
  • 10.0.0 (full support)

Template Literal Revision

When a template literal is immediately preceded by an expression, it is called a tagged template literal. A tagged template comes in handy when you want to parse a template literal with a function. Consider the following example:

function fn(string, substitute) {   if(substitute === 'ES6') {     substitute = 'ES2015'   }   return substitute + string[1]; }  const version = 'ES6'; const result = fn`$  {version} was a major update`;  console.log(result);    // → ES2015 was a major update

In this code, a tag expression — which is a regular function — is invoked and passed the template literal. The function simply modifies the dynamic part of the string and returns it.

Prior to ES2018, tagged template literals had syntactic restrictions related to escape sequences. A backslash followed by certain sequence of characters were treated as special characters: a \x interpreted as a hex escape, a \u interpreted as a unicode escape, and a \ followed by a digit interpreted as an octal escape. As a result, strings such as "C:\xxx\uuu" or "\ubuntu" were considered invalid escape sequences by the interpreter and would throw a SyntaxError.

ES2018 removes these restrictions from tagged templates and instead of throwing an error, represents invalid escape sequences as undefined:

function fn(string, substitute) {   console.log(substitute);    // → escape sequences:   console.log(string[1]);     // → undefined }  const str = 'escape sequences:'; const result = fn`$  {str} \ubuntu C:\xxx\uuu`;

Keep in mind that using illegal escape sequences in a regular template literal still causes an error:

const result = `\ubuntu`; // → SyntaxError: Invalid Unicode escape sequence

Support for Template Literal Revision

Chrome Firefox Safari Edge
62 56 11 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
62 56 11 No 8.2 62

Node.js:

  • 8.3.0 (requires the –harmony runtime flag)
  • 8.10.0 (full support)

Wrapping up

We’ve taken a good look at several key features introduced in ES2018 including asynchronous iteration, rest/spread properties, Promise.prototype.finally(), and additions to the RegExp object. Although some of these features are not fully implemented by some browser vendors yet, they can still be used today thanks to JavaScript transpilers such as Babel.

ECMAScript is rapidly evolving and new features are being introduced every so often, so check out the list of finished proposals for the full scope of what’s new. Are there any new features you’re particularly excited about? Share them in the comments!

The post New ES2018 Features Every JavaScript Developer Should Know appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]