专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

前端开发必须掌握的JavaScript异步编程

ins518 2024-10-05 20:33:53 技术文章 13 ℃ 0 评论

  目前,JavaScript的编程元素相比一些主流的编程语言比如C#和Java还是不算多,但是应用它的语法糖很多,如果不熟悉这些语法糖,很多的代码将难以阅读与理解,就比如同样是链式编程,在代码组织上,C#上就比JavaScript来得简明,也可能是个人习惯,觉得JavaScript以后容纳更多的编程元素后将更难以阅读,但它的发展还远没有到头,按现在的发展趋势,今后它有可能成为一流的、应对所有应用的最佳开发工具。

  对于异步编程,就是通常我们俗称的AXAJ即Asynchronous Javascript And XML(异步JavaScript和XML),我们都认为它一种具体的编程技术,实质上,我们应该理解AXAJ为一系列的支持异步编程的方法、工具的集合(也可以说是一种思想),这个集合里面有很多的对象和方法来帮助我们用JavaScript语言实现异步编程,比如XMLHTTPRequest、JQuery里面的AXAJ、Promise、Fetch、Axios、async/await等等,Javascript语言本身是不支持异步编程的,但JavaScript的宿主浏览器支持,所以Javascript通过回调函数将需要异步处理的调用放到浏览器的事件循环队列,这样就实现了异步编程。

  我们怎样使用JavaScript来实现异步编程的呢?

  一、网页嵌套、定时器

  1998年,我使用HTML + CGI开发一个Web应用,使用Web页面框架来实现局部刷新,程序提交数据时候将调用页面底部的响应而提交按钮所在的页面,这样只是底部显示变化数据【其他页面如有数据变化,这个页面的处理程序也一样可以处理】而整个页面不动。

  使用定时器来实现异步编程在以前也用到过,类似在一个消息队列中不断加入消息检测,满足条件了就执行处理函数。

  这些都太老旧了。

  二、XMLHTTPRequest

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>XMLHttpRequest</title>
</head>
<body>
    <script>
        //1、创建Ajax对象
        let xhr=new XMLHttpRequest();
        //2、设置请求信息
        //xhr=open(请求方式,请求地址,是否异步)
        xhr.open('Post','http://127.0.0.1/ES6_Base/ASD.php',true);
        //3、配置请求完成事件
        xhr.setRequestHeader('content-type','application/x-www-form-urlencoded')
        xhr.onreadystatechange=function(){
            if(xhr.readyState===4){
                if(xhr.status>=200 && xhr.status<300){
                        let res=JSON.parse( xhr.responseText );
                        console.log( "用户名:"+res.Name );
                        console.log( "密码:"+res.Password );
                }else{
                         console.log(xhr.status);
                }
            }
        }
        //4、发送请求
        xhr.send("Name=ASD&Password=123");
    </script>
</body>
</html>

  PHP脚本:

<?php
    $returnArr=[];//准备返回的数组
    $Name = $_POST['Name'];
    $Password = $_POST['Password'];
 
    $returnArr['Name']=$Name;
    $returnArr['Password']=$Password;
 
    header('Content-type:text/json');
    echo json_encode($returnArr,JSON_UNESCAPED_UNICODE);  
?>

  返回结果:

用户名:ASD
密码:123

  最开始的时候,大家都习惯使用这样的编码来实现异步编程,但这样原生的编码方式不方便,如果有嵌套那么代码也显得冗长,不方便阅读,现在几乎没有人再使用它。

  Jquery的出现让编程方式做了很大的改变。

  三、JQuery的AXAJ方法(函数)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="jquery-3.4.1.min.js" charset="utf-8"></script>
    <title>Jquery</title>
</head>
<body>
    <script>
        $.ajax({
                url:'http://127.0.0.1/ES6_Base/ASD.php',
                data:{
                        "Name":"ASD",
                        "Password":"123"
                        },
                type:'POST',
                async:true,
                success:function (JsonData) {
                    console.log("用户名:"+JsonData['Name']);						
                    console.log("密码:"+JsonData['Password']);						
                }
        });							 
    </script>
</body>
</html>

  返回和上面一样的结果。

  JQuery也是封装了原生AXAJ的API,使用起来方便快捷。

  在很多框架里可以看到JQuery的身影,它们都集成了JQuery,比如LayUI、EasyUI等。

  JQuery自2006年诞生起在很长一段时间里相当流行,是当时使用最为广泛和频繁的函数库,但目前逐渐在淡出。主要原因可能是回调地狱、ECMAScript6下有更好的异步编程模式等等。

  四、Promise对象

  Promise是ECMAScript6中提供的用于解决异步编程的类。

  使用它有助于书写优雅、复杂的异步任务,改善了异步编程的困难,避免了回调地狱,比传统的解决方案回调函数和事件更合理和更强大。

  通俗地理解,就是使用同步编程的写法可以解决异步编程的问题。

        const p=new Promise( (resolve,reject)=> {
            //执行任务
            //任务成功时执行resolve,可以包含结果
            //任务失败时执行reject,可以包含错误提示信息
        }).then( ( value )=>{
            //获取resolve的结果再进一步进行处理
        }).catch( ( reason )=>{
            //获取reject的信息并进一步处理
        })

  上面是我们经常使用的写法,也可以使用下面的写法,效果与上面一样。

        const p=new Promise( (resolve,reject)=> {
            //执行任务
            //任务成功时执行resolve,可以包含结果
            //任务失败时执行reject,可以包含错误提示信息
        }).then( ( value )=>{
            //获取resolve的结果再进一步进行处理
        } ,( reason )=>{
            //获取reject的信息并进一步处理
        })

  注意点:

  (1) promise对象有三种状态,分别是pending(准备中)、fulfilled(成功/完成)、rejected(拒绝/失败)。

  (2) promise对象的状态改变是一次性的,也就是只能改变一次,要么pending -> fulfilled,要么pending -> rejected。

  (3) 通过执行resolve改变promise对象状态由pending -> fulfilled,执行reject改变promise对象状态由pending -> rejected。

  (4) then方法返回的是promise对象,promise支持方法调用的链式写法。

  (5) 当promise的状态为rejected或者执行体中的代码异常时执行catch函数。

  虽然promise相比Jquery的Axaj有很大的改观,的确也解决了异步编程中常遇到的回调地狱、回调函数的嵌套问题,但实际写代码过程中,依然觉得promise如果then很多的情况下阅读代码不是很方便。

  promise在现在的异步编程中应用相当普遍。

  五、asyc/await

  比较而言,ECMAScript7(2016年)中应用于异步编程的asyc/await则人性化了许多,真正实现了异步处理同步化(写法)。

        async GetListTask(id){
            let result;
            result=await this.GetListTask1(id)    //等待获取任务列表1
            //......处理            
            result=await this.GetListTask2(id)    //等待获取任务列表2
            //......处理            
        }

  注意点:

  (1) 如果函数中包含了await但函数前面没有冠以async则程序会报错。

  (2) 函数前面直接使用asyc但函数体内无await程序并不会报错,但无意义,asyc/await必须成对出现才有意义。

  现在很多设计异步编程的的开发都使用asyc/await。

  六、Generator(生成器)

  通过生成器返回的迭代器对象,根据需要执行next()方法来控制程序进度。

        function* ToDo() {
            console.log("执行第一步任务");
            yield 111;
 
            console.log("执行第二步任务");
            yield 222;
 
            console.log("执行第三步任务");
        }
 
        const myToDo = ToDo(); //获取生成器对象
        console.log(myToDo.next().value); // 输出:111,到yield 111为止
        
        console.log(myToDo.next().value); // 输出:222,到yield 222为止
        
        console.log(myToDo.next().value); // 输出:undefined,任务执行完毕
 
输出结果:
执行第一步任务
111
执行第二步任务
222
执行第三步任务
undefined

  通过迭代来控制:

        for(let item of myToDo) {
            console.log( item ) 
        }
 
结果输出:
执行第一步任务
111
执行第二步任务
222
执行第三步任务

  这种方式有特定的应用场景,感觉还是习惯asyc/await

  七、fetch对象

  fetch被称为下一代的Ajax技术,采用promise方式来处理数据,代码结构比较简洁,fetch不是对XMLHTTPRequest对象的进一步的封装,是新的异步编程实现方法和规范。

  fetch默认不会带有cookie,需要添加配置项,fetch不能再请求过程中检测进度。

        fetch('http://127.0.0.1:8018/JS/ECMAScript6/ES6_Base/ASD.php',{method:'post',body:'Name=ASD&Password=123',headers:{ 'Content-Type':'application/x-www-form-urlencoded' }  }).then(data=>{
           //text()是fetch的一部分,它返回的是promise对象,用于获取后台返回的数据
            return data.text();
        }).then(result=>{
            //这里获得最终的数据
            console.log(result);
        })
 
结果输出:
{"Name":"ASD","Password":"123"}

  八、Axios

  Axios是一个基于Promise、用于浏览器和Node.js的HTTP客户端,它也是对原生XMLHTTPRequest对象的promise封装。这个异步编程元素必须重点掌握。

  1、axios的常用方法

  ⑴. 发送 GET 请求:axios.get(url[, config])。

  ⑵. 发送 POST 请求:axios.post(url[, data[, config]])。

  ⑶. 发送 PUT 请求:axios.put(url[, data[, config]])。

  ⑷. 发送 DELETE 请求:axios.delete(url[, config])。

  ⑸. 发送 HEAD 请求:axios.head(url[, config])。

  ⑹. 发送 OPTIONS 请求:axios.options(url[, config])。

  ⑺. 发送 PATCH 请求:axios.patch(url[, data[, config]])。

  axios 提供的各种方法分别对应HTTP规范中定义的不同请求方法,它们各自有不同的用途,通常与 RESTful API 设计标准保持一致,也是为了让axios操作更加语义化。

  对应的后端操作(NodeJS):

constexpress=require('express');
constmyapp=express();
 
myapp.get('/api',(req,res)=>{
	//处理get请求
});
 
myapp.post('/api',(req,res)=>{
	//处理post请求
});
 
myapp.put('/api',(req,res)=>{
	//处理put请求
});
 
myapp.delete('/api',(req,res)=>{
	//处理delete请求
});
 
myapp.patch('/api',(req,res)=>{
	//处理patch请求
});
 
myapp.head('/api',(req,res)=>{
	//处理head请求
});
 
myapp.options('/api',(req,res)=>{
	//处理OPTIONS请求
});

  在平常的开发中,我们经常使用的是get和post方法,通常是在这两个操作中完成对数据的增、删、改、查。

  2、axios的编码范式

  axios是一个基于Promise的HTTP客户端,在JavaScript代码中使用axios库时,有几种常见的编码范式。
  以axios的get方法为例,下面是常见的用法。
  ⑴基本使用

        axios.get('http://127.0.0.1:1234/user')
            .then(function (res) {
                console.log(res);
            })
            .catch(function (err) {
                console.error(err);
        });
 
        axios({
            url:'http://127.0.0.1:1234/user',
            method:'get'
        }).then(function (res) {
                console.log(res);
            })
            .catch(function (err) {
                console.error(err);
        });

  ⑵带有参数

        axios.get('http://127.0.0.1:1234/user', {
            params: {
            ID: 2
            }
        })
        .then(function (res) {
            console.log(res);
        })
        .catch(function (err) {
            console.error(err);
        });
 
        axios({
            url:'http://127.0.0.1:1234/user', 
            method:'get',
            params: { ID: 2 }
        })
        .then(function (res) {
            console.log(res);
        })
        .catch(function (err) {
            console.error(err);
        });

  ⑶通过async/await使用

        async function getUserInfo() {
            try {
                const res = await axios.get('http://127.0.0.1:1234/user?ID=2');
                console.log(res);
            } catch (err) {
                console.error(err);
            }
        }
 
        getUserInfo();

  ⑷创建axios实例

        const myAxios = axios.create({
            baseURL: 'http://127.0.0.1:1234',
            timeout: 2000,
        });
 
        myAxios.get('/user?ID=2')
            .then(function (res) {
                console.log(res);
        }).catch(function (err) {
            console.error(err);
        });        

  3、axios的链式编程
  很多前端开发者喜欢链式编程写法。
  链式编程中如果任何一个请求发生错误,最后的catch方法都将捕获并统一处理错误,这是方便的地方,但是过多的链式编程给调试带来不必要的麻烦。

        Mock.mock('http://127.0.0.1:1234/user?name=vue',{
            'user':{'Id':1 }
        })
 
        Mock.mock('http://127.0.0.1:1234/user?userId=1',{
            'info':{'title':'vue + elementUI + Admin' }
        })        
 
        axios.get('http://127.0.0.1:1234/user?name=vue')
        .then(function (res) {
            return res.data.user.Id;
        })
        .then(function (userId) {
            return axios.get('http://127.0.0.1:1234/user?userId='+userId);
        })
        .then(function (res) {
            console.log(res.data.info.title);
        })
        .catch(function (err) {
            console.err(err);
        });

  程序最后输出:vue + elementUI + Admin。

  4、axios处理并发请求

        Mock.mock('http://127.0.0.1:1234/getUserName',{            
                        'name':'vue'             
        })
 
        Mock.mock('http://127.0.0.1:1234/getUserInfo',{
                        'Framework':'vue + elementUI + Admin' 
        })
 
        function getUserName() {
            return axios.get('http://127.0.0.1:1234/getUserName');
        }
 
        function getUserInfo() {
            return axios.get('http://127.0.0.1:1234/getUserInfo');
        }
 
        Promise.all([getUserName(), getUserInfo()])
        .then(function (res) {
            const arr1 = res[0];
            const arr2 = res[1];
            // 处理结果
            console.log(arr1.data.name);
            console.log(arr2.data.Framework);
        });
 
        axios.all([getUserName(), getUserInfo()])
        .then(function (res) {
            const arr1 = res[0];
            const arr2 = res[1];
            // 处理结果
            console.log(arr1.data.name);
            console.log(arr2.data.Framework);
        });

  如果不使用axios.all()或者Promise.all()来执行,让两个axios单独执行,那么可能由于axios完成时间的不同,导致不同的axios获取结果存在先后顺序;但是放在axios.all()或者Promise.all()中来执行,那么就是等待这两个都完成再一起输出结果。

  现在流行的写法是使用箭头函数:

        axios.all([getUserName(), getUserInfo()])
            .then(res=>{
                console.log(res);
                for(let i=0;i<res.length;i++){
                    console.log(res[i].data);
                }
        });        

  或者:

        axios.all([getUserName(), getUserInfo()])
            .then(axios.spread(
                (res1,res2) => {
                console.log(res1.data.name);
                console.log(res2.data.Framework);
        }));    

  5、创建axios实例
  一般写法:

        const myAxios=axios.create();
 
        myAxios.defaults.baseURL="http://127.0.0.1:1234";
 
        myAxios.request({
            method:'get',
            url:'/user',
            timeout: 2000,
            headers: {'X-Custom-Header': 'foobar'}            
        }).then(
            res=>{ console.log(res.data.name); }
        )

  或者:

        const myAxios=axios.create({
            baseURL:"http://127.0.0.1:1234",
            timeout: 2000,
            headers: {'X-Custom-Header': 'foobar'}            
        });
 
        myAxios.request({
            method:'get',
            url:'/user',
        }).then(
            res=>{ console.log(res.data.name); }
        )

  6、axios拦截器
  拦截操作就是在axios进行操作之前进行一些系统需要统一处理的操作,比如可以验证用户、检查请求的合法性、检查数据的完整性等等。

        Mock.mock('http://127.0.0.1:1234/user',{            
                        'name':'JavaScript'             
        })
 
        const myAxios=axios.create({
            baseURL:"http://127.0.0.1:1234",
            timeout: 2000,
            headers: {'X-Custom-Header': 'foobar'}            
        });
 
        myAxios.interceptors.request.use(
            request=>{
                console.log("检查请求...");
                return request
            }
        )
 
        myAxios.interceptors.response.use(
            response=>{
                console.log("检查返回数据...");
                return response
            }
        )
 
        myAxios.request({
            method:'get',
            url:'/user',
        }).then(
            res=>{ console.log(res.data.name); }
        )

  上面执行的结果显示如下:

检查请求...
检查返回数据...
JavaScript

  这篇文章合成了我的CSDN博客上的两篇文章《AXAJ与JavaScript中的异步编程元素》和《axios》。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表