jueves, 28 de octubre de 2021

Java Servlet Filters

Java Servlet Filters

 
  Let´s try filters on servlets, this sample takes three filters, the filter_3 clean the value of the parameter named dangerousParamName. The order of flow is determined accord the order in the web.xml file.
The request pass the parameters to and the servlet process them, but the request pass first through the filter_1 then filter_2 and finally the filter_3 that clean the value of the dangerousParamName.

Project structure

The servlet run on Apache8 server

The web.xml file with filters order 1 --> 2 --> 3

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name>servlet-and-filters</display-name>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

       <filter>
        <filter-name>Filter_1</filter-name>
        <filter-class>com.bext.filter.Filter_1</filter-class>
        <init-param>
            <param-name>Filter_1_InitParam</param-name>
            <param-value>Filter_1_InitParam-value-123</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Filter_1</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>Filter_2</filter-name>
        <filter-class>com.bext.filter.Filter_2</filter-class>
        <init-param>
            <param-name>Filter_2_InitParam</param-name>
            <param-value>Filter_2_InitParam-value-345</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Filter_2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>Filter_3</filter-name>
        <filter-class>com.bext.filter.Filter_3</filter-class>
        <init-param>
            <param-name>Filter_3_InitParam</param-name>
            <param-value>Filter_3_InitParam-value-333</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Filter_3</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>com.bext.servlet.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>
</web-app>


TestServlet.java

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

    private static final long serialVersionUID = -2063502095847255667L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<b>=== TestServlet running. doGet ===</b><br/>");
        Enumeration<String> parameterNames = req.getParameterNames();
        if (parameterNames.hasMoreElements()) {
            while (parameterNames.hasMoreElements()) {
                String name = parameterNames.nextElement();
                String value = req.getParameter(name);
                out.println("name: " + name + ", value: " + value + "<br/>");
            }
        } else {
            out.println("--- TestServlet Has Not Parameters ---<br/>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }

    private void performTask(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<b>=== TestServlet running. doPost ===</b><br/>");
        Enumeration<String> parameterNames = request.getParameterNames();
        if (parameterNames.hasMoreElements()) {
            while (parameterNames.hasMoreElements()) {
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);
                out.println("name: " + name + ", value: " + value + "<br/>");
            }
        } else {
            out.println("--- TestServlet Has Not Parameters ---<br/>");
        }
    }
}

Filter_1.java and Filter_2.java

package com.bext.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class Filter_1 implements Filter {

    FilterConfig filterConfig = null;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        String param = filterConfig.getInitParameter("Filter_1_InitParam");
        System.out.println("Init param: " + param);
    }
    
    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
       String name = "";
       response.setContentType("text/html");
       PrintWriter out = response.getWriter();
       out.println("<b>Filter_1</b> <br/>InitParam: " + filterConfig.getInitParameter("Filter_1_InitParam"));
       out.println("<br/><br/>Filter_1 Parameters:<br/>");
       Enumeration<String> parameterNames = request.getParameterNames();
       if (parameterNames.hasMoreElements()) {
           while (parameterNames.hasMoreElements()) {
               name = parameterNames.nextElement();
               String value = request.getParameter(name);
               out.println("Param Name: " + name + ", Value: " + value + "<br/>");
           }
       } else {
           out.println("--- Filter_1 Has Not Parameters ---<br/>");
       }
       
       out.println("<br><b>Filter_1</b> Before doFilter(req, resp)</br><hr/>");
       chain.doFilter(request, response);
       out.println("<hr/><br><b>Filter_1</b> After doFilter(req, resp)</br>");
    }

}

Filter_3.java

package com.bext.filter;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class Filter_3 implements Filter {

    static class FilteredRequest extends HttpServletRequestWrapper {

        /* These are the characters allowed by the Javascript validation */
        static String allowedChars = "+-0123456789#*";

        public FilteredRequest(HttpServletRequest request) {
            super((HttpServletRequest) request);
        }

        public String sanitize(String input) {
            String result = "";
            for (int i = 0; i < input.length(); i++) {
                if (allowedChars.indexOf(input.charAt(i)) >= 0) {
                    result += input.charAt(i);
                }
            }
            return result;
        }

        public String getParameter(String paramName) {
            String value = super.getParameter(paramName);
            if ("dangerousParamName".equals(paramName)) {
                value = sanitize(value);
            }
            return value;
        }

        public String[] getParameterValues(String paramName) {
            String values[] = super.getParameterValues(paramName);
            if ("dangerousParamName".equals(paramName)) {
                for (int index = 0; index < values.length; index++) {
                    values[index] = sanitize(values[index]);
                }
            }
            return values;
        }
    }
       
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<br><b>Filter_3</b> Before doFilter(req, resp)</br><hr/>");
        chain.doFilter(new FilteredRequest((HttpServletRequest) request), response);
        out.println("<hr/><br><b>Filter_3</b> After doFilter(req, resp)</br>");
    }


}


 Test Url
 
http://localhost:8080/servlet-and-filters/test?doGetParameter=323&anotherParam=Lucas&dangerousParamName=01thistextwillbedeletedgarbage2345garbage5?%C2%A1re678kdfs90
 
Browser output:
 
Filter_1
InitParam: Filter_1_InitParam-value-123

Filter_1 Parameters:
Param Name: doGetParameter, Value: 323
Param Name: anotherParam, Value: Lucas
Param Name: dangerousParamName, Value: kjgheiun123455?¡re34

Filter_1 Before doFilter(req, resp)

Filter_2
InitParam: Filter_2_InitParam-value-345

Filter_2: Parameters:
Param Name: doGetParameter, Value: 323
Param Name: anotherParam, Value: Lucas
Param Name: dangerousParamName, Value: kjgheiun123455?¡re34

Filter_2 Before doFilter(req, resp)


Filter_3 Before doFilter(req, resp)

=== TestServlet running. doGet ===
name: doGetParameter, value: 323
name: anotherParam, value: Lucas
name: dangerousParamName, value: 12345534


Filter_3 After doFilter(req, resp)


Filter_2 After doFilter(req, resp)


Filter_1 After doFilter(req, resp)
 
Changing the filter order 3 --> 2 --> 1
 
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name>servlet-and-filters</display-name>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <filter>
        <filter-name>Filter_3</filter-name>
        <filter-class>com.bext.filter.Filter_3</filter-class>
        <init-param>
            <param-name>Filter_3_InitParam</param-name>
            <param-value>Filter_3_InitParam-value-333</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Filter_3</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>Filter_2</filter-name>
        <filter-class>com.bext.filter.Filter_2</filter-class>
        <init-param>
            <param-name>Filter_2_InitParam</param-name>
            <param-value>Filter_2_InitParam-value-345</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Filter_2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

       <filter>
        <filter-name>Filter_1</filter-name>
        <filter-class>com.bext.filter.Filter_1</filter-class>
        <init-param>
            <param-name>Filter_1_InitParam</param-name>
            <param-value>Filter_1_InitParam-value-123</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Filter_1</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>com.bext.servlet.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>
</web-app>

 
 
 Browser output:
Filter_3 Before doFilter(req, resp)

Filter_2
InitParam: Filter_2_InitParam-value-345

Filter_2: Parameters:
Param Name: doGetParameter, Value: 323
Param Name: anotherParam, Value: Lucas
Param Name: dangerousParamName, Value: 012345567890

Filter_2 Before doFilter(req, resp)

Filter_1
InitParam: Filter_1_InitParam-value-123

Filter_1 Parameters:
Param Name: doGetParameter, Value: 323
Param Name: anotherParam, Value: Lucas
Param Name: dangerousParamName, Value: 012345567890

Filter_1 Before doFilter(req, resp)

=== TestServlet running. doGet ===
name: doGetParameter, value: 323
name: anotherParam, value: Lucas
name: dangerousParamName, value: 012345567890


Filter_1 After doFilter(req, resp)


Filter_2 After doFilter(req, resp)


Filter_3 After doFilter(req, resp)
 
text

eot

martes, 26 de octubre de 2021

Javascript async/await

 Javascript async/await

using async await , we use a promise in createPost(post) an asynchronous process that add a post retarded by 200 millisecond, so the getPost() process before and It would not be aware of the update thanks to the async-await that waits the execution of the getPosts() process until the createPost finishes the posts update.


const posts = [{title: 'post one', body: 'body of post one'},
              {title: 'post two', body: 'body of post two'}];

function getPosts(){
  setTimeout(() => {
      let output = '';
      posts.forEach( (post, index) => {
        console.log(index + ' ' + post.title );
        output += `<li>${post.title}</li>`;
      });
      document.body.innerHTML = output;
  }, 100);
};

function createPost(post){
   return new Promise((resolve,reject) => {
      setTimeout(() => {
        posts.push(post);
        resolve();
      }, 200)
   })
};

async function processPost(){
   await createPost({title: 'post three', body:'body of post three'});
   getPosts();
}

processPost();

 
browser output:
  • post one
  • post two
  • post three
  •  
    with out the async / await code
     
    browser output:
  • post one
  • post two
  •  
    this is because the getpost() process run before posts are updated with the third record.
     
    async function fetchUser(){
       const res = await fetch('https://jsonplaceholder.typicode.com/users');
       const data = await res.json();
       console.log(data);
    }

    fetchUser();

     
    browser inspector output:

    XHR GEThttps://jsonplaceholder.typicode.com/users
    Array(10) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…} ]
    0: Object { id: 1, name: "Leanne Graham", username: "Bret", … }
    1: Object { id: 2, name: "Ervin Howell", username: "Antonette", … }
    2: Object { id: 3, name: "Clementine Bauch", username: "Samantha", … }
    3: Object { id: 4, name: "Patricia Lebsack", username: "Karianne", … }
    4: Object { id: 5, name: "Chelsey Dietrich", username: "Kamren", … }
    5: Object { id: 6, name: "Mrs. Dennis Schulist", username: "Leopoldo_Corkery", … }
    6: Object { id: 7, name: "Kurtis Weissnat", username: "Elwyn.Skiles", … }
    7: Object { id: 8, name: "Nicholas Runolfsdottir V", username: "Maxime_Nienow", … }
    8: Object { id: 9, name: "Glenna Reichert", username: "Delphine", … }
    9: Object { id: 10, name: "Clementina DuBuque", username: "Moriah.Stanton", … }
    length: 10

     
    eot

    lunes, 25 de octubre de 2021

    Javascript Promises

     Javascript Promises

    var output = '';
    var errorLog = '';

    function task(msg, msec){
        output += msg;
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const error = true;
                !error ? resolve()
                       : reject( 'Error-task-' + msg)
            }, msec);
        });
    }

    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => errorLog += err))
           .catch(err => errorLog += err))
    .catch(err => errorLog += err)
    .finally( () => {display(); displayErrorLog();})

    function display(){
      console.log('output: ' + output);
    }

    function displayErrorLog(){
        console.log('ErrorLog: ' + errorLog);
    }

      lets refactor the previos sample of list post, now with promise

    const posts = [{title: 'post one', body: 'body of post one'},
                  {title: 'post two', body: 'body of post two'}];

    function getPosts(){
      setTimeout(() => {
          let output = '';
          posts.forEach( (post, index) => {
            console.log(index + ' ' + post.title );
            output += `<li>${post.title}</li>`;
          });
          document.body.innerHTML = output;
      }, 100);
    };

     function createPost(post){
       return new Promise((resolve, reject) => {
           setTimeout(() => {
              posts.push(post);

              const error = false;
              !error ? resolve()
                     : reject('Error: something went wrong');
           }, 200);
       });
    };

    createPost({title: 'post three', body:'body of post three'})
     .then( getPosts)
     .catch( err => console.log(err));
     

     Another sample, In this case the procedure task put the main procedure of output buffer inside the async function, so the output spected is in disorder defined by the timeout of the procedures because all are executed asynchronously, That´s because the process of buffer output must be out and before the setTimeout (asynchrous process).

    var output = '';

    function task(msg, msec){
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                output += msg;

                const error = false;
                !error ? resolve()
                       : reject( 'Error - task - ' + msg)
            }, msec);
        });
    }

    task('task1->', 30)
    .then( task2('task2->', 10)
           .then(task('task3->', 50)
                .then(display)))


    function display(){
        console.log(output);
    }
    // output: task2->task1->task3->

     Putting the process of buffer output before the asynchronous process (setTimeout), So the execution order of the task are executed as expected independently of the taken time for each of them.

    var output = '';

    function task(msg, msec){
        output += msg;
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const error = false;
                !error ? resolve()
                       : reject( 'Error - task - ' + msg)
            }, msec);
        });
    }

    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)))


    function display(){
        console.log(output);
    }
    // output: task1->task2->task3-

     Adding the catch management for the promises, forcing to fail to see the catch, look the order of the error messages, first the task3 then task2 and finally task1

    var output = '';

    function task(msg, msec){
        output += msg;
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const error = true;
                !error ? resolve()
                       : reject( 'Error - task - ' + msg)
            }, msec);
        });
    }

    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => console.log(err)))
           .catch(err => console.log(err)))
    .catch(err => console.log(err))


    function display(){
        console.log(output);
    }
    // output: Error - task - task3->
    //         Error - task - task2->
    //         Error - task - task1->
     
    Let´s separate the error log buffer display in order to manage the flow. 
    The output is ErrorLog with the buffer error empty, why? because the displayErrorLog() is executed asynchronous before all the other process.
     
    var output = '';
    var errorLog = '';

    function task(msg, msec){
        output += msg;
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const error = true;
                !error ? resolve()
                       : reject( 'Error-task-' + msg)
            }, msec);
        });
    }

    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => errorLog += err))
           .catch(err => errorLog += err))
    .catch(err => errorLog += err)

    displayErrorLog();

    function display(){
      console.log('output: ' + output);
    }

    displayErrorLog();
    function displayErrorLog(){
      console.log('ErrorLog: ' + errorLog);
    }
    // output: ErrorLog:

     To check the error log delay the execution of the displayErrorLog function

    ... 
    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => errorLog += err))
           .catch(err => errorLog += err))
    .catch(err => errorLog += err)

    displayErrorLog();

    function display(){
      console.log('output: ' + output);
    }

    displayErrorLog();
    function displayErrorLog(){
      setTimeout(()=>{
        console.log('ErrorLog: ' + errorLog);
      }, 40);
    }
    // output: ErrorLog: Error-task-task3->Error-task-task2->Error-task-task1->

     Or integrate into the build task pattern, this way the displayErrorLog will be secuenciated synchronous into the flow process. notice that .then(displayErrorLog) is not properly used, because .then is executed on resolve situation.

    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => errorLog += err))
           .catch(err => errorLog += err))
    .catch(err => errorLog += err)
    .then(displayErrorLog)

    function display(){
      console.log('output: ' + output);
    }

    displayErrorLog();
    function displayErrorLog(){
        console.log('ErrorLog: ' + errorLog);
    }
    // output: ErrorLog: Error-task-task3->Error-task-task2->Error-task-task1->

    Use displayErrorLog into the last .catch to see all the error buffer log.

    ... 
    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => errorLog += err))
           .catch(err => errorLog += err))
    .catch(err => {errorLog += err; displayErrorLog();})

    .finally   To show the buffers at the end of the secuence use .finally

    ...
    task('task1->', 30)
    .then( task('task2->', 10)
           .then(task('task3->', 0)
                .then(display)
                .catch(err => errorLog += err))
           .catch(err => errorLog += err))
    .catch(err => errorLog += err)
    .finally( () => {display(); displayErrorLog();})

    // output: output: task1->task2->task3->
    //         ErrorLog: Error-task-task3->Error-task-task2->Error-task-task1->

     Sample with callbacks

    const userLeft = false
    const userWatchingCatMeme = true

    function watchTutorialCallback(callback, errorCallback){
        if(userLeft){
            errorCallback({
                name: 'User left',
                message: ':('
            })
        } else if (userWatchingCatMeme) {
            errorCallback({
                name: 'User Watching Cat Meme',
                message: 'message error watching cat meme'
            })
        } else {
            callback('Thanks and Subscribe')
        }
    }

    watchTutorialCallback((message) =>{
        console.log('Success: ' +  message)
    }, (error) => {
        console.log(error.name + ' ' + error.message)
    })
     

    Success: Thanks and Subscribe

    Same but with promise

    const userLeft = false
    const userWatchingCatMeme = false

    function watchTutorialPromise(){
      return new Promise((resolve, reject) => {
        if (userLeft){
            reject({
                name: 'User left',
                message: ':('
            })
        } else if (userWatchingCatMeme) {
            reject({
                name: 'User Watching Cat Meme',
                message: 'message error watching cat meme'
            })
        } else {
            resolve('Thanks and Subscribe')
        }
      });
    }

    watchTutorialPromise().then((message) => {
        console.log('Success: ' +  message)
    }).catch((error) => {
        console.log(error.name + ' ' + error.message)
    })

    Promise.All

    const taskOne = new Promise((resolve,reject) => {
        resolve('task1->')
    })

    const taskTwo = new Promise((resolve,reject) => {
        resolve('task2->')
    })

    const taskThree = new Promise((resolve,reject) => {
        resolve('task3->')
    })

    Promise.all([
        taskOne, taskTwo, taskThree
    ]).then((message) => {
        console.log( message)
    })

    //output: Array(3) [ "task1->", "task2->", "task3->" ]
     

     Setting different process time

    const taskOne = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('task1->')
        }, 2000)
    })

    const taskTwo = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('task2->')
        }, 30)
    })

    const taskThree = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('task3->')
        }, 10)
    })

    Promise.all([
        taskOne, taskTwo, taskThree
    ]).then((message) => {
        console.log( message)
    })

    //output: Array(3) [ "task1->", "task2->", "task3->" ]
     

     Promise.race

    const taskOne = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('task1->')
        }, 2000)
    })

    const taskTwo = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('task2->')
        }, 30)
    })

    const taskThree = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('task3->')
        }, 10)
    })

    Promise.race([
        taskOne, taskTwo, taskThree
    ]).then((message) => {
        console.log( message)
    })

    //output: task3->

    Promise All with fetch json

    const promise1 = Promise.resolve("promise1 resolved")
    const promise2 = 10;
    const promise3 = new Promise((resolve,reject) =>
                                  setTimeout(resolve, 100, 'promise3'))
    const promise4 = fetch('https://jsonplaceholder.typicode.com/users')
       .then(val => val.json())

    Promise.all([ promise1, promise2, promise3, promise4])
    .then(value => console.log(value))

    Browser Inspector Output
     
    Array(4) [ "promise1 resolved", 10, "promise3", (10) […] ]
    0: "promise1 resolved"
    1: 10
    2: "promise3"
    3: Array(10) [ {…}, {…}, {…}, … ]
    0: Object { id: 1, name: "Leanne Graham", username: "Bret", … }
    1: Object { id: 2, name: "Ervin Howell", username: "Antonette", … }
    2: Object { id: 3, name: "Clementine Bauch", username: "Samantha", … }
    3: Object { id: 4, name: "Patricia Lebsack", username: "Karianne", … }
    4: Object { id: 5, name: "Chelsey Dietrich", username: "Kamren", … }
    5: Object { id: 6, name: "Mrs. Dennis Schulist", username: "Leopoldo_Corkery", … }
    6: Object { id: 7, name: "Kurtis Weissnat", username: "Elwyn.Skiles", … }
    7: Object { id: 8, name: "Nicholas Runolfsdottir V", username: "Maxime_Nienow", … }
    8: Object { id: 9, name: "Glenna Reichert", username: "Delphine", … }
    9: Object { id: 10, name: "Clementina DuBuque", username: "Moriah.Stanton", … }
    length: 10
    <prototype>: Array []
    length: 4
    <prototype>: Array []
     

     

     

     

    eot