Introduction
Demo of different fetch
constructions client-side. We just render the ids in the browser to view the result of testing some fetch chaining strategies. We used a public API (endpoint: "https://reqres.in/api/users/" supporting CORS
and https
or "https://jsonplaceholder.typicode.com/users/"). The data is saved in cache. A link to a live demo using surge. < https://ndrean.github.io/asyn-promise/.
-
Simple loop with the construction:
async/await
-
Parallel fetching by using
promise.all()
on an array of promises -
Sequential fetching by using
reduce()
on an array of promises -
Batch fetching with
array.slice()
and thenpromise.all()
to fetch by batch -
cache: short explanation of the implementation of cache-storing of GET requests
- Setup and usage of Workbox
Live demo app at:
Sources:
-
https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker
-
https://golb.hplar.ch/2018/01/A-closer-look-at-the-Cache-API.html
-
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce
-
https://decembersoft.com/posts/promises-in-serial-with-array-reduce/
fetch and cache API
async await
withtry/catch
.- The cache API is also implemented with
await
.
Parallel fetching
Given an array usersIds = [1,...n]
, we map to an array of promises indexed by usersIds
. These promises are simply given by fetch(uri/{id})
and rendered in the browser. We then call Promise.all([arrayOfPromises])
.
Sequential fetching
Suppose we have a fixed number of tasks/promises. If we simply do promise1.then(promise2).then(promise3)
, we can't capture the return values. Then we can do (display
is just a browser rendering method):
Promise.resolve()
.then((r) => {
return promise1.then((r) => display(...));
})
.then((r) => {
return promise2.then((r) => display(...));
})
.then((r) => {
return promise3.then((r) => display(...));
});
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce
If we have dynamic promises, we put all the promises in an array and use reduce()
to collapse the array of promises into a single promise chain.
The reduce()
method executes a provided callback function callback
which takes the previousValue
and currentValue
which iterates over the array. The function stores the result in the accumulator
which will be the previousValue
. It uses and initial value. Here, we take Promise.resolve([])
, a promise that always resolves to an empty array as the initial value. Then accumulator === initialValue
on the first time through the callback.
We will capture the result and push it to the array, further used as arrayOfResults
. It is
then used to display results in the browser with our display
method.
promises
.reduce((promiseChain, currentPromise) => {
return promiseChain.then((result) => {
return currentPromise.then((currentResult) => {return [...result, currentResult}]);
// we return an array that is used as "arrayOfResults"
});
}, Promise.resolve([])) // the initial value
// we use the result array (simply indexes) to render in the browser
.then((arrayOfResults) => {
console.log(arrayOfResults);
arrayOfResults.forEach((result) => {
console.log(result);
display("#resu8", result, "Seq :");
});
});
Batch fetching
We use the same Promise.all
but this time we slice the array of 'usersId' into smaller arrays; we then map to a promise on each subarrays to produce subarrays of promises. Finaly, we run promise.all([])
.
cache
We can use the cache to store http GET
(only GET). To do so, we just declare a cache
by giving it a name and add a stringified key/value {request: reponse}
(we just need to declare request in case of an http fetch).
const request = new Request(url);
const response = await fetch(request);
// create/open a new named cache
const newCache = await caches.open("cacheName");
// add the {request:response}
await newCache.add(request);
// await cache.put(request, response) if not from web
To review it, we can inspect Application/cache in Chrome or display it in the console with the snippet below:
// we look for the request in the cache
const responseFromCache = await caches.match(request);
// then we parse it
const matchedCachedObj = await responseFromCache.json();
// and review in the console:
console.log("cacheName", matchedCachedObj.data.email);
AXIOS
Implementation of alternative library Axios
: looping and post.
Workbox
TODO
Bundling browserify
If we want to work with indexedDB
, we can use the npm
package idb. To do so, we have a working file index.js that contains the code using idb
. To use it, we need to require the module idb
, so we need to bundle it. We can use a quick tool such as the npm
package browserify:
Browserify lets you require('modules') in the browser by bundling up all of your dependencies.
browserify
will bundle index.js inside a new file bundle.js , that will launched by the browser with a script in index.html. We then can use the idb
module with a require('idb') inside index.js.
yarn add idb browserify
browserify index.js -o bundle.js
Bundling Webpack
npm
packages
Install We initialize yarn
(the flag -y will skip all the questions) with yarn init (-y)
and then install webpack
with all needed dependencies (--dev
or -D
to install locally):
yarn add webpack webpack-cli webpack-dev-server copy-webpack-plugin html-webpack-plugin css-loader style-loader -D
Since we used Axios
, instead of adding the following cdn script in the index.html file:
script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js" /script
we import the package with yarn add axios
and add the import where needed in our .js files:
+ import axios from "axios";
Directories setup
Our files system should look like
|-.cache (if Parcel)
|-node_modules
|-.gitignore
|-readme.md
|-package.json
|-yarn.lock
|-/dist
|-|-index.html
|-|-main.js
|-/scr
|-|-/img
|-|-|-icon-512x512.png
|-|-|-icon-192x192.png
|-|-|-...
|-|-index.html
|-|-index.js
|-|-functions.js
|-|-forms.js
|-|-styles.css
|-|-...
Gitignore
We add .cache node_modules dist
in the .gitignore file.
Exclude browsers that does not accept ES5
#package.json
"browserslist": [ "since 2017-06" ]
webpack.config.js
Here is a simple Webpack configuration file:
# webpack.config.js
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"), // our distribution directory will be: `/dist
filename: "main.js", // the name of the final JS file
},
devServer: {
contentBase: "./src/", //absolute path recommendedpath.resolve(__dirname, "src"),
watchContentBase: true, // full page reload, for the static html change
},
devtool: "inline-source-map", // helper to locate errors
plugins: [
// clean folder after production building
new CleanWebpackPlugin(),
//will automatically inject bundle js into ./dist/index.html (output folder)
new HtmlWebpackPlugin({
template: "src/index.html",
filename: "index.html",
}),
//copy assets not explicitely referenced from our Javascript
new CopyWebpackPlugin([
{ from: "src/img", to: "img/" }
]),
],
module: { // to compile the CSS files
rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }],
},
};
Note on CSS
We added the packages css-loader
(for the import
) and style-loader
(to inject as style in pages) packages firstly (declared in package.json
).
We have defined how to import CSS files in webpack.config.js, namely how to find them and them compile and inject the styles:
module: {rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }]}
- We add
import "./styles.css"
within index.js
#index.js
+ import "./styles.css"
We added bootstrap which needs the packages jquery and popper.js and relies on css-loader since we imported Compiled CSS.
#index.js
import "bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
- we remove (because of
style-loader
) the link in the header of the index.html file:
script link rel="stylesheet" type="text/css" href="./styles.css" script
Hot reload
We configured:
devServer: {
contentBase: "./src/", //absolute path recommendedpath.resolve(__dirname, "src"),
watchContentBase: true, // full page reload, for the static html change
},
and use with webpack --mode development --hot --watch
compilation mode.
package.json
npm scripts in Webpack is configured within the file webpack.config.js
. With the CLI, we can run commands like yarn webpack --mode development --config webpack.config.js
to compile in development mode for example. Webpack will use the default configuration file webpack.config.js
of webpack if present.
the
webpack.config.js
file is picked by default if present, so we can not mention it
Instead of running commands for webpack-cli
in the terminal, we can add helpers by adding npm scripts
to the package.json
file.
# package.json
{
"scripts":{
"clean": "rm dist/*",
"dev": " webpack --mode development --hot --watch (--config webpack.config.js)",
"build": "webpack -p --mode production",
"serve": "webpack-dev-server"
}
}
We can now run in a terminal on of the following commands:
yarn clean (this empties the '/dist' folder)
yarn dev (bundles in development mode)
yarn build (bundles in production mode when ready to minimize and output in the '/dist' folder)
yarn serve (runs a development server)
Remove Link to index.js
We will compile the project to the main.js file in the /dist folder: this is declared in webpack.config.js
with (output: [...,filename: "main.js"])
).
script type="module" scr="index.js"> /script
Compile and launch web-pack-dev-server
To compile the project, we run in the terminal the npm scripts helpers that we defined: yarn dev
or yarn build
once it's finished.
Once the compilation is made, with this config, we will serve the files with webpack-dev-server
so that the --watch
mode is automatically on, meaning that it will recompile automatically whenever files change (HTML or CSS or Javascript) so that we don't have to reload the page or stop/start the web server.
Notes for Parcel.js
- Firstly, we need a file
index.html
and create a directory/src
and put all our files inside. The main js file will be named/src/index.js
. The link to this file should be declared in the index.html file without type="module" (which is needed forwebpack
otherwise).
script type="module" src="src/index.js"> /script
- Then since we use
async/await
, we can limit the accepted of browsers to those who accept ES5, through the filepackage.json
( if we use the bundlerParcel
)
#package.json
"browserslist": [ "since 2017-06" ]