Reprinted from: The Road to big Front-end Technology Welcome to follow

Simplify your code with top-level await

JavaScript was originally designed as a single-threaded, synchronous language, meaning that programs need to run step by step without waiting for external resources or time-consuming calculations. This synchronization behavior will result in an error if the script requires such resources or calculations. This blocks all other processes in the queue from running, whether or not they are dependent on those blocked tasks.

But long ago, JavaScript introduced a mechanism for executing the rest of its code while waiting for external resources or time-consuming tasks. This asynchronous behavior is implemented by using callbacks or promises on functions.

What are callbacks and promises

I’ll explain these concepts in code. If you already know what callbacks and promises are, skip to the top-level await section and the sample application.

The callback

In a callback, one function is passed as an argument to another function; Therefore, the second argument in the addEventListener function below is the callback function. This callback will wait for the first click event to occur before executing the second parameter.

const x = document.getElementsByTagName('Button');
x[0].addEventListener('click'.() = >{alert("I was clicked")})
Copy the code

This waiting behavior makes the code asynchronous. Unlike synchronous code, it can run step by step without waiting for resources to download or time-consuming processes to end. Note, however, that not all callbacks are asynchronous.

Promises

Promises are like callbacks in that they append functions to returned objects. But Promises are also different from callbacks. Promises are specifically designed for asynchronous methods. They have only one argument and a then () function to get the return result. In addition, it can chain-attach multiple. Then () and catch () functions.

fetch('www.xyz.com/api')
.then((res) = >{let x = res.data; //do something with received data})
.catch((err) = >{console.log(err)})
Copy the code

Promises use event queues and strictly follow the order in which asynchronous tasks are linked.

Async/await

Async/await is a syntactic improvement to Promises to avoid chain calls. It makes code clearer and easier to understand. The await keyword pauses code until Promises succeed or fails.

async function asyncwaitcode(){
  let getData = await axios('www.xyzdata.org/api')
  console.log(getData.data)
}
Copy the code

What is the top-level await

All of the above examples make the code in the function block asynchronous, and none of them work at the module level.

However, asynchronous behavior can be implemented at the module level. A module that uses a top-level await notifies the module’s consumer to continue executing its own code after asynchronously initializing its namespace.

The following sample code shows how to use top-level await.

About the App

The application pulls the most popular news data from the news API and presents it in the browser. Users can also search news data by related search terms. Before we begin, a few points to note:

  • Top-level await is supported in Node 13.3 and later
  • Top-level await is only supported in ECMAScript modules, but Node.js and Express are CmmonJS modules. CmmonJS does not support the top-level await feature. So I’ll use it in my codeimportRather thanrequire
  • Prior to Node 14.x, top-level await cannot be used directly, requiring –harmony to be enabled
  • Cyclic references to modules can cause deadlocks

To build the App

  1. Create the toplevelawait directory
$ mkdir toplevelawait
Copy the code
  1. npm initInitialize the
$ npm init
Copy the code

3. Add “type”: “module” to package.json to support ECMAScript modules

"author": "",
"license": "ISC",
"type": "module",
Copy the code

4. Create a SRC directory under the toplevelawait directory. Note the use of MJS as the file suffix.

$ touch app.mjs
$ touch exp.mjs
$ ls -1 src
app.mjs
exp.mjs
Copy the code
  1. The installation relies on AXIOS, EJS, and Express
$ npm install axios ejs express --save
Copy the code
  1. exp.mjsAdd the following code to:
import express from "express"
export const exp = await express();
Copy the code

Note that we are using await without async. Express instances are initialized before being exported to other modules. In this way, you can wait for an instance of a module to complete initialization before executing code that depends on that module.

If a module contains a top-level await, execution of its parent module will stop until the promise completes. But its sibling module will continue to execute in the same sync mode as before.

Note that modules in Node.js are also loaded synchronously, meaning they cannot wait for resources to load asynchronously. However, you can implement asynchronous waiting by adding the await keyword before statements that load or process resources.

Increase the news APIs

The application uses two free news apis to get the data. Both apis support fallback dependency behavior; If one API fails, another API gets the data. Both apis use API keys:

  • News API
  • GNews API

Insert the following code into the app.mjs file. The previous target imports AXIos and express instances initialized in exp. Js. The next section sets up the view engine for presentation in the browser.


import { exp } from "./exp.mjs";
import axios from "axios"

exp.set("view engine"."ejs");
// dependency fall back
let response = "";
let site = true;
try{
   response = await axios('https://newsapi.org/v2/top-headlines?country=us&apiKey=your-api-key');  
 }
 catch{
  response = await axios("https://gnews.io/api/v3/top-news? token=your-api-key");
  site = false;
 }
 // Get top news
exp.get('/'.function(req,res){
  let response0 = response.data.articles  
  res.render('main.ejs', {response0: response0, site:site})
 })
 // search news
exp.get('/search'.function(req,res){
  res.render("searchnews.ejs")  
})
exp.get('/result'.async(req, res)=>{
  let x = req.query.newtitlesearch;
  let response1 = {}
  let data = {}
  try{
    let url = 'https://newsapi.org/v2/everything?q='+x+'&apiKey=your-api-key'
    response1 =  await axios(url);
  }
  catch{
    let url = 'https://gnews.io/api/v3/search? q='+x+'&token=your-api-key'
    response1 =  await axios(url)
  }
  res.render('result.ejs', {response1: response1.data.articles, site: site})  
})
exp.listen(3000)

Copy the code

The most important part is the try catch block, which uses the top-level await to wait for Axios to fetch the data. If, for any reason, AXIos cannot get data from the first API, the application will use the second API to get data. Once it gets the data from the API, Express can render it on the home page.

try{
   response = await axios('https://newsapi.org/v2/top-headlines?country=us&apiKey=your-api-key');
 
 }
 catch{
  response = await axios("https://gnews.io/api/v3/top-news? token=your-api-key");
 
 }
Copy the code

Next we provide a route for the user to search:

// search news
exp.get('/search'.function(req,res){
  res.render(".. /src/view/searchnews.ejs")})Copy the code

Finally, another path displays the search results:

exp.get('/result'.async(req,res)=>{
  let x = req.query.newtitlesearch;
  let response1 = {}
  let data = {}
  try{
    let url = 'https://newsapi.org/v2/everything?q='+x+'&apiKey=your-api-key'
    response1 =  await axios(url);
  }
  catch{
    let url = 'https://gnews.io/api/v3/search? q='+x+'&token=your-api-key'
    response1 =  await axios(url)
  }
  res.render('.. /src/view/result.ejs', {response1: response1.data.articles , site: site})  
})
Copy the code

Write front-end interface

The final part of the application is to write four.EJS HTML files for the front page. Save these files in the ‘View’ folder:

//header.ejs
<! DOCTYPEhtml>
<head>
    <title>newapiapp</title>
    <link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" ></head><body>  
    <nav class="navbar navbar-default">
              <div class="container-fluid">
                  <div class="navbar-header">
                      <a class="navbar-brand" href="#">News app</a>
                  </div>
                  <div class="collapse navbar-collapse">
                      <ul class="nav navbar-nav navbar-right">
                             <li><a href="/">Main</a></li>                
                              <li><a href="/search">Search</a></li>      
                      </ul>      
                  </div>
              </div>  
    </nav>

Copy the code
//main.ejs <%include('header'); %> <%let rows = response0%> <%let siterep = site%><div name "container">
  <div class="row text-center" style="display:flex; flex-wrap:wrap">
    <% for(let i = 0; i < rows.length; i++){%>
      <div class="col-md-3 col-sm-6 ">
                          <div class="thumbnail" >
                            <a href="<%-rows[i].url %>">
                              <img src = "<%= siterep ? rows[i].urlToImage : rows[i].url %>">
                            </a>                            
                          </div>
                          <div><%= rows[i].title%></div>                
                        </div>The < %} % ></div>  
</div>
Copy the code
//searchnews.ejs <%- include('header'); % ><h1>Search news </h1>
  <form action="/result" method="Get">
      <input type ="text" placeholder="news title search" name="newtitlesearch"></input>
        <input type="submit" placeholder="submit"></input>        
   </form>

Copy the code
//result.ejs <%- include('header'); %> <%let rows = response1%> <%let siterep = site%><div name "container">
  <div class="row text-center" style="display:flex; flex-wrap:wrap">

    <% for(let i = 0; i < rows.length; i++){%>
      <div class="col-md-3 col-sm-6 ">
                          <div class="thumbnail" >
                            <a href="<%-rows[i].url %>">
                              <img src = "<%= siterep ? rows[i].urlToImage : rows[i].url %>">
                            </a>                            
                          </div>
                          <div><%= rows[i].title%></div>                  
                        </div>The < %} % ></div>  
</div>

Copy the code

Run the app

Now that the APP is complete, you can try it out. If you’re using Node.js v13.3 through v14.0, run:

$ node --harmony-top-level-await app.js
Copy the code

If you’re using Node.js v14.0 or above, you don’t need to — Harmony flag:

$ node app.js
Copy the code

If you have successfully built this app, congratulations on learning a new JS feature.

You can get more information in the ECMAScript TC39 top-level await proposal.