L o a d i n g . . .
SHIWIVI-文章

//sunny forever
while(life<end){
love++;
beAwesome :)}

    <
  • 主题:
  • + -
  • 清除背景
  • 禁用背景

JavaScript异步与AJAX

字数:14464 写于:2021-04-01
最新更新:2021-04-01 阅读本文预计花费您42分钟
文中所声明对象和语法基于javaScript

迭代器

迭代器(iterator)是一种可以为各种不同的数据结构提供统一遍历操作的接口,JAVA、C++、Python、PHP等很多语言都有迭代器的概念,对象只要部署了iterator接口,就可以进行遍历操作(如用for语句进行遍历)。

迭代器的遍历

迭代器遍历的原理是这样的:

  1. 创建一个指针对象,指向当前数据结构的起始位置;
  2. 调用指针对象的next方法,指针指向数据结构的第一个成员;
  3. 重复调用next方法,指针后移动,直至指向最后一个成员;

以最常见的遍历数组为例:我们可以用for…of等语句对其进行遍历,是因为数组中原生内置了iterator接口。

 const songs=['夜曲','手写的从前','反方向的钟','晴天','最长的电影'];
    console.log(songs);
iterator接口

按照其原理,先定义一个对象来获取该Symbol.iterator属性,找到该对象中包含的next方法。调用这个next()方法,它会返回一个包含done和value属性的对象。

const songs=['夜曲','手写的从前','最长的电影','晴天','明明就'];
        // console.log(songs);
        let iterator=songs[Symbol.iterator]();
        console.log(iterator);
        console.log(iterator.next());
iterator中的next方法

重复调用next()方法,该方法会依次遍历数组成员并返回多个包含done和value属性的对象,遍历完数组后,再返回一个done属性值为true,value属性值为undefined的对象,表示数组遍历结束。

遍历结束

在JavaScript中,原生自带iterator接口的数据结构有:
ArrayMapSetStringTypedArray函数的 arguments 对象NodeList 对象

自定义遍历对象

对于没有内置iterator接口的数据结构,可以仿照其原理,手写一个iterator接口,并且自定义需要遍历或者允许被遍历的内容。

按照其原理,添加iterator接口的步骤分为:

  1. 添加Symbol.iterator属性,它是以一个函数的形式包含;
  2. 在其中添加next()方法;
  3. next()方法会依次返回一个包含done和value属性的对象;
  4. 如果遍历结束,则返回一个done属性值为true,value属性值为undefined的对象;
完整代码:
 //遍历输出对象的songs属性
        const Rapper={
            name:'王以太',
            shows:['Chengdu','Hangzhou','Shenzhen'],
            songs:['三思而后行','阿司匹林','童言无忌','目不转睛','人间天堂'],
            [Symbol.iterator](){
                let index=0;//索引号
                return{
                    next: ()=>{
                        if(index< this.songs.length){
                            const result={
                                value:this.songs[index],
                                done:false};
                                index++;
                                return result;
                            }
                            else{
                                return{
                                    value:undefined,
                                    done:true
                                }
                            }
                        }
                    }
                }
            };
          //遍历输出
        for(let m of Rapper){
        console.log(m);
    }
python、C++写个类似的接口也很简单,不过对复杂点的数据结构比较麻烦

生成器函数

生成器函数允许我们定义一个包含自有迭代算法的函数,返回一种称为 Generator 的迭代器,生成器函数需要使用function *语法声明

function * gen(){ console.log("执行生成器"); } const g=gen();//返回一个名为Generator的迭代器,但不执行console.log语句 console.log(g); g.next();//需要执行迭代器的next的方法才会执行生成器,才开始执行生成器中的console.log语句

yield关键字

yield 关键字用于生成器函数的暂停和恢复,当执行Generator迭代器的next()方法时,开始执行生成器中的代码,一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法再次被调用,暂停时yield 关键字后面的表达式的值返回给生成器的调用者,类似于一个基于生成器的版本的 return 关键字

[rv] = yield [expression];

  • rv 返回传递给生成器的 next() 方法的可选值,以恢复其执行
  • expression:定义通过迭代器协议从生成器函数返回的值。如果省略,则返回 undefined
function * gen(){ console.log("第一次执行"); yield 111; console.log("第二次执行"); yield 222; console.log("第三次执行"); yield 333; console.log("第四次执行"); yield 444; } const iterator=gen(); console.log(iterator.next());//第一次执行next()方法,输出"第一次执行",并返回第一个yield表达式的值111 iterator.next();//第二次执行next()方法,执行到第二个yield表达式为止 iterator.next(); iterator.next();

next()方法传参

next() 方法接受一个参数用于修改生成器内部状态,传递给 next() 的参数值会被 yield 接收。传递给第一个 next() 的值会被忽略,而传递给第二个 next() 的值会被第一个 yield 接收,依此类推

function * gen(arg){ console.log(arg);//生成器函数本身的实参 let first=yield 111; console.log(first);//"BBB" let second=yield 222; console.log(second);//"CCC" let third=yield 333; console.log(third);//"DDD" yield 444; } const iterator=gen("生成器实参"); iterator.next();//第一个 next() 的实参会被忽略,不传参 iterator.next("BBB"); iterator.next("CCC"); iterator.next("DDD");

异步调用

由于生成器函数每次执行到yield语句就会暂停,利用这个特性我们可以进行对资源的异步顺序加载,

实现:先加载"用户数据",等待"用户数据"加载完毕再加载"订单数据",最后加载"商品数据",案列使用定时器模拟异步环境 function getUsers(){ setTimeout(()=>{ let user="用户数据"; iterator.next(user);//加载完的数据通过next()方法传给生成器函数,并开始加载下一项数据 },1000) } function getOrders(){ setTimeout(()=>{ let order="订单数据"; iterator.next(order); },1000) } function getGoods(){ setTimeout(()=>{ let goods="商品数据"; iterator.next(goods); },1000) } function * gen(){ let users=yield getUsers(); console.log(users);//处理数据 let order=yield getOrders(); console.log(order); let goods=yield getGoods(); console.log(goods); } const iterator=gen(); iterator.next();//初次执行next()方法

Promise

Promise是ES6新增的异步编程解决方案,用于封装异步任务,并且可以根据异步任务的成功/失败,对结果和数据进行灵活处理,此外,Promise支持链式调用,能很好地解决回调地狱问题

Promise对象

Promise本质上是一个函数返回的对象,代表了一个异步操作的成功或者失败,该对象中有两个关键属性:PromiseStatePromiseResult

PromiseState 即Promise的状态,Promise有三种状态:

  • pending 未决定的
  • resolved/fulfilled 决定的,符合的(成功)
  • rejected 拒绝的(失败)

该属性只能被修改一次,并且修改完毕后也只有一个结果(resolved或rejected)和结果数据

PromiseResult即Promise封装的异步任务成功/失败的结果数据,只能通过resolve()reject()函数修改,并且可以传递给then()方法,Promise成功时结果数据一般使用value为实参名,失败则使用reason作为实参名

实例化

使用new关键字进行实例化,实例化时接收一个函数类型的参数,该函数又接收两个函数类型的形参,resolve()用于异步任务执行成功时调用,reject()用于异步任务执行失败时调用

const p=new Promise((resolve,reject)=>{ //封装异步任务 if(异步任务执行成功){ resolve(data); } else{ reject(data); } });

当resolve被调用后,Promise状态会被修改为成功,随后执行then()方法的第一个回调函数。reject则会将Promise状态会被修改为失败,随后执行then()方法的第二个回调函数

p.then((value)=>{ //Promise状态为成功时执行 },(reason)=>{ //Promise状态为失败时执行 });
p.catch(resaon=>{ //只会在Promise状态为失败时执行 });
此外,Promise状态为失败时也可以使用catch()方法,该方法被单独封装用于处理失败情况,底层依旧调用了then()方法

API

执行流程

通过new创建Promise对象,对象初始状态为pending状态,然后执行内部的异步操作,执行成功则调用resolve()函数,将Promise对象状态修改为resolved状态,此时如果调用了then()方法,则将执行then()方法中的第一个回调函数,执行完该回调函数后返回一个新的promise()对象。如果异步任务执行失败,则调用reject()函数,将Promise对象状态修改为rejected状态,此时如果调用了then()方法,则将执行then()方法中的第二个回调函数,执行完该回调函数后返回一个新的promise()对象

构造函数

Promise((excutor){})
该构造函数接收一个执行器函数,内部定义了resolve()和reject()函数,这两个函数不属于Promise对象内部,执行器函数会在对象创建后立即执行(同步调用)

快速获取Promise对象

以下方法属于Promise函数对象,可以直接调用

  • Promise.resolve(value)
    value可以为任意类型的数据,或者一个Promise对象
    返回一个成功的Promise对象,但如果传入了一个Promise对象,则其状态取决于传入对象的状态,如果传入的Promise对象为成功,则返回的对象也为成功状态,反之亦然。该方法用于快速将一个数据转换为一个状态为成功的Promise对象
    const p1=Promise.resolve(100);// const p2=Promise.resolve(new Promise((resolve,reject)=>{ reject();//由于传入的Promise对象状态为rejected,依次p2也为rejected状态 }))
  • Promise.reject(value)
    永远返回一个rejected状态的Promise对象,即便传入的值是成功状态的Promise

  • Promise.all(promise[])
    参数为一个Promise数组,只有当该数组中的所有Promise对象状态都为resolved时,该方法才返回一个resolve状态的Promise对象,且该对象的PromiseResult为数组中的Promise对象成功结果组成的数组。如果该数组中有任意一个Promise对象状态为rejected,则该方法返回的Promise对象状态将修改为rejected,且该对象的PromiseResult为数组中第一个失败成员的返回的PromiseResult

    let p1=Promise.resolve(123) let p2=Promise.reject("aaa") let p3=Promise.reject({test:0}) let p=Promise.all([p1,p2,p3]); console.log(p);//p对象的PromiseResult为"aaa"
      p2.catch(reason=>{});
      p3.catch(reason=>{});
      p.catch(reason=>{});</div>
    

否则返回一个rejected状态的Promise

  • Promise.race(promise[])
    参数为一个Promise数组,该方法返回的Promise对象的状态取决中传入数组中第一个执行完异步任务的Promise对象状态,且值也为其PromiseResult
修改Promise状态的方法
  • resolve函数
  • reject函数
  • throw 函数
执行多个回调

Promise允许指定多个回调,即可以指定多个then()方法

let p=new Promise((resolve,reject)=>{ resolve("go"); }); p.then(value=>{ console.log("第一个回调"+value); }); p.then(value=>{ console.log("第二个回调"+value); });
then()方法返回值

then()方法会返回一个新的Promise对象,该对象的状态和值取决于then()方法所执行的回调函数

  • 如果该回调函数返回一个Promise对象,则then()方法返回的Promise对象状态与其相同
  • 如果该回调函数无return语句(默认返回undefined),或者返回一个非Promise对象的值(如:返回字符串等),则总是返回一个fulfilled状态的Promise
  • 如果该回调函数throw异常,则返回rejected状态的Promise对象
let p=new Promise((resolve,reject)=>{ resolve(); }) let result1=p.then(value=>{throw '异常'});//状态为rejected let result2=p.then(value=>{return 'aaa'});//状态为fulfilled let result3=p.then(value=>{ return new Promise((resolve,reject)=>{ reject('error'); }) });//状态为rejected
链式调用

由于then()方法返回了一个新的Promise对象,因此该对象也可以调用then()方法,而该then()方法又返回一个新的Promise对象,又能调用then()方法,因此then()方法可以链式调用

let p=new Promise((resolve,reject)=>{ resolve('success'); }) p.then(value=>{ console.log(value);//success }).then(value=>{ console.log(value);//由于上一个then()无返回值,因此其返回的Promise对象的PromiseResult为undefined,因此输出undefined })
异常穿透

当then()方法进行链式调用时,只需要在最后进行异常处理,前面的链式调用出现的任何异常,都会被传到最后处理异常的回调函数中进行处理(异常穿透特性)

let p=new Promise((resolve,reject)=>{ resolve('success'); }) p.then(value=>{ console.log(value); throw '异常' //抛出异常 }).then(value=>{ console.log(value);//由于上一个Promise状态为rejected,then()方法第一个回调不执行 }).then(value=>{ console.log(value);//同上,不执行 }).then(value=>{ console.log(value);//同上,不执行 }).catch(reason=>{ console.warn(reason);//在链式最后进行异常处理 })
中断Promise链

当then()方法进行链式调用时,如果想在某个节点中断Promise链,只需要在该节点返回一个pending状态的Promise对象,因为处于pending状态的Promise对象不会触发then()方法的执行,而其他任何返回值、异常抛出等操作都会使链式继续进行。注意:中断以后的Promise链,最后的异常捕获会依旧执行

let p=new Promise((resolve,reject)=>{ resolve('success'); }) p.then(value=>{ console.log(111);//输出111 throw 'error' }).then(value=>{ return new Promise(()=>{});//返回pending状态的Promise对象 }).then(value=>{ console.log(222);//上一个Promise对象为pending状态,不执行then()方法 }).catch(reason=>{ console.warn(reason);//但由于异常穿透,该语句会继续执行 })

async与await

async函数

async函数可以帮助我们用更简洁的方式写出基于 Promise 的异步行为,用于包裹await表达式,该函数会返回一个Promise对象,对象的状态取决于async函数的返回值:

  • 如果该函数无return语句(默认返回undefined),或者返回一个非Promise对象的值(如:返回字符串等),则返回一个fulfilled状态的Promise对象
  • 如果该函数返回一个Promise对象,则async函数返回的Promise对象状态与其相同
  • 如果该函数throw异常,则返回rejected状态的Promise对象

await表达式

await表达式必须用async函数包裹,其右侧一般为Promise对象,也可以为其他值

  • 如果为其他值,则await返回值与其相同
  • 如果为Promise对象,则返回值为该对象fulfilled状态下的PromiseResult属性值
  • 如果为Promise对象,且状态为rejected,则必须用try…catch进行异常捕获处理
async function main(){ try{ let result=await new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve('success') },1000) }) console.log(result); } catch(e){ console.warn(e) } } main();

AJAX

简介

AJAX为Asynchronous JavaScript And XML的缩写,即异步 JavaScript 和 XML,用于更新数据,而不需要重载(刷新)整个页面

XMLHttpRequest对象

Ajax 需要依赖于 XMLHttpRequest 对象,主流浏览器都支持 XMLHttpRequest 对象,只需要new即可
let xmlhttp=new XMLHttpRequest();

E5和IE6等低版本IE浏览器不支持XMLHttpRequest 对象,而需要使用ActiveX 对象,因此如果需要兼顾低版本IE浏览器,则需要做兼容性检查

var xmlhttp; if (window.XMLHttpRequest) { xmlhttp = new XMLHttpRequest();//主流浏览器 } else { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");//低版本IE浏览器 }

XMLHttpRequest对象方法

  • open(请求类型,url,async,user,psw) 书写一个请求
    • 请求类型:GET 或POST
    • url为文件位置
    • async:是否异步,值为true(异步)或 false(同步)
    • user:用户名称(可选)
    • psw: 用户密码(可选)
  • send() 发送请求,用于GET请求
  • send(string)发送请求,用于POST请求
  • abort()取消当前请求
  • setRequestHeader() 向要发送的报头添加标签/值对
  • getResponseHeader() 返回特定的头部信息
  • getAllResponseHeaders() 返回头部信息

XMLHttpRequest对象属性

  • onreadystatechange 定义当 readyState 属性发生变化时被调用的函数
  • readyState 表示当前 XMLHttpRequest 的状态
    • 0:UNSET,尚未调用open方法,请求未初始化,
    • 1:OPEND,open方法被调用,服务器连接已建立
    • 2:HEADERS_RECEIVED,send()方法被调用,请求已收到
    • 3:LOADING,正在处理请求,response属性已经包含部分数据
    • 4:DONW,请求已完成且响应已就绪
  • responseText 以字符串返回响应数据
  • responseXML 以 XML 数据返回响应数据
  • statusText 返回状态文本(比如 “OK” 或 “Not Found”)
  • status 返回请求的状态号

发起请求

发起get请求

如果get请求需要携带参数,可以直接在URL地址后以键值对的形式拼接查询字符串,多个参数使用&拼接

xhr.open('GET','http://shiwivi.com')
xhr.open('GET','http://shiwivi.com?id=1&author=shiwivi')
let xhr=new XMLHttpRequest(); xhr.open('GET','http://shiwivi.com') xhr.send(); xhr.onreadystatechange=function(){ if(xhr.readyState===4&&xhr.status===200){ console.log(xhr.responseText) } }
发起post请求

AJAX发起post请求主要需要如下流程:

  1. 创建xhr对象
  2. 调用open()方法初始化请求
  3. 设置content-type属性,指定请求头所含数据MIME类型为application/x-www-form-urlencoded,即进行了URL编码的二进制数据
  4. 调用send()方法,并指定请求所需要包含的数据
  5. 监听onreadystatechange事件
var xhr=new XMLHttpRequest(); xhr.open('POST','http://shiwivi.com'); xhr.setRequestHeader('content-Type','application/x-www-form-urlencoded') xhr.send('id=1&author=shiwivi'); xhr.onreadystatechange=function(){ if(xhr.readyState===4&&xhr.status===200){ console.log(xhr.responseText) } }
URL中只允许出现英文字母、数字、标点符号,在拼接查询字符串时,如果我们在URL中拼接了中文字符,或者其他被预定义的字符,则浏览器会对其进行URL编码,其中中文字符会被编码为%xx%xx%xx形式(x为16进制数),JavaScript中可以使用encodeURI('字符串')对该字符串进行编码,使用decodeURI('%xx%xx')对该字符串进行解码

JQuery中的Ajax

JQuery对XMLHttpRequest进行了封装

发起get请求

$.get(url,[data],[function(){…}]) 发起get请求

  • url为请求地址,必须
  • data为请求所携带的参数,非必须,参数也可以以查询字符串的形式拼接到URL后
  • function可以指定请求成功执行的回调,非必须
$(function(){ $.get('http://shiwivi.com',function(res){ console.log(res)//res用于接收返回的数据 }) })
$.get('http://shiwivi.com',{id:1,author:''shiwivi},function(res){...}) //等价于 $.get('http://shiwivi.com?id=1&author=shiwivi',function(res){...})
发起post请求

$.post(url,[data],[function(){…}]) 发起post请求

  • url为提交数据地址,必须
  • data为所要提交的数据,非必须
  • function可以指定请求成功执行的回调,非必须
$(function(){ $.post('http://xxxx.com',{name:'shiwivi',type:'blog'},function(res){ console.log(res) }) })
get与post通用

$.ajax({
type:’get/post/put/delete’,
url:’’,
data:{ },
dataType:’html/text/json/xml/script/jsonp’,
success:function(res,textStatus,jqXHR){ },
error:function(jqXHR,textStatus,error){ }
})

  • type为请求方式,可选get/post/put/delete,默认get
  • url为请求地址
  • data为请求携带的数据
  • dataType为请求数据类型
  • success为请求成功执行的回调函数,参数依次为响应内容(根据dataType作了处理)、文本形式的响应状态、jqXHR对象(里面包含状态码等信息)
  • error为请求失败执行的回调函数,参数依次为jqXHR对象、文本形式的错误信息(eg:error、timeout、abort)、捕获的异常对象(对于http请求为http状态的文本部分eg:Not Found)
$.ajax({ type:'GET', url:'http://shiwivi.com', data:{id:1}, success:function(res,textStatus,jqXHR){ console.log(res);//服务器返回的数据 console.log(textStatus);//success console.log(jqXHR.status);//200 }, error:function(jqXHR,textStatus,error){ console.error(jqXHR.status);//404 console.log(textStatus);//error console.log(error);//Not Found } })
上一篇:正则表达式
下一篇:Git与Github
z z z z z