Ajax 异步JS&xml - XMLHttpRequest详解从XHR到Promise

0x01 引言:

XMLHttpRequest对象是当今所有AJAX和Web 2.0应用程序的技术基础

0x02 代码示例讲解

示例代码:

const url = "https://c-ssl.duitang.com/uploads/item/202002/24/20200224112148_nknod.jpg" //要渲染的图片链接
const img = document.getElementById('img');


req.open('GET', url);   //以GET方式请求url资源
req.responseType = 'blob';
req.onload = function () {        //设定一个回调函数
    if (req.status === 200) {
        img.src = url; //设置img标签的src为指定url
    }
    else {
        console.log('failed');
    }
}
req.onerror = function () {
    console.log("network error");
}
req.send();

0.前言

注意,我们这里引入XHR对象的状态码与状态值,用来确认我们对象已经运行到了哪一步状态

ajax状态码(status)与状态值(readyState)区别

readyState,是指运行AJAX所经过的几种状态,无论访问是否成功都将响应的步骤,可以理解成AJAX运行步骤,使用“ajax.readyState”获得
status,是指无论AJAX访问是否成功,由HTTP协议根据所提交的信息,服务器返回的HTTP头信息代码,使用“ajax.status”获得

readyState

也就是说可以把status理解为http状态码的映射,readyState是xhr对象进入不同步骤的状态,如初始化
0:初始化,XMLHttpRequest对象还没有完成初始化  
1:载入,XMLHttpRequest对象开始发送请求  
2:载入完成,XMLHttpRequest对象的请求发送完成  
3:解析,XMLHttpRequest对象开始读取服务器的响应  
4:完成,XMLHttpRequest对象读取服务器响应结束

1创建对象

const req = new XMLHttpRequest();

首先创建XMLHttpRequest对象
对象创建完成后,

2声明要异步请求的url

const url = "https://c-ssl.duitang.com/uploads/item/202002/24/20200224112148_nknod.jpg"

这里我放了一张02的美图哈哈哈哈!

3抓取DOM目标标签

const img = document.getElementById('img');

我们在index.html中设置了img id = ‘img’以供js抓取

4设置请求参数

req.open('GET', url);

param1:请求方法 param2:请求资源的url
注意!!!open方法不会发送请求!

5设置响应类型

req.responseType = 'blob'

这里很重要哦!!!好多bug例如资源无法解析,数据格式不对就是响应类型没有设置好
responseType值的类型可为如下

数据类型
‘’ DOMString (这个是默认类型)
arraybuffer ArrayBuffer对象
blob Blob对象
document Document对象
json JavaScript object, parsed from a JSON string returned by the server
text DOMString (video流可能用这个)

什么是blob对象?

Binary large Object
blob对象表示 类似文件的原始数据 不可变 注意,不一定是js的原生数据类型

6设置回调函数

一般用于监听事件,可叫做监听器
onload是在send请求发出且获得response会被调用

//---------页面加载成功(READYSTATE4)url设置回调函数----------//
req.onload = function () {        //设定一个回调函数
    if (req.status === 200) {
        img.src = url; //设置img标签的src为指定url
    }
    else {
        console.log('failed');
    }
}
//----------页面加载失败的回调函数--------------------------------//
req.onerror = function () {
    console.log("network error");
}

什么叫回调函数?

机制

⑴定义一个回调函数;
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
百科解释:

回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。
回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。同时,有一些需求必须要使用回调函数来实现。
最著名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。

7send请求

记住嗷!open并不会发送请求,发送请求的是send函数

req.send();

为我们提供了操作http的机会,使我们有能力完成本应由浏览器完成的发送和接受http
数据的能力。

代码汇总

const url = "https://c-ssl.duitang.com/uploads/item/202002/24/20200224112148_nknod.jpg" //要渲染的图片链接
const img = document.getElementById('img');


req.open('GET', url);   //以GET方式请求url资源
req.responseType = 'blob';
req.onload = function () {        //设定一个回调函数
    if (req.status === 200) {
        img.src = url; //设置img标签的src为指定url
    }
    else {
        console.log('failed');
    }
}
req.onerror = function () {
    console.log("network error");
}
req.send();

0x03 JS - Promise

No Promise 和 Promise的区别,图片直观感知。
Ajax 异步JS&xml - XMLHttpRequest详解从XHR到Promise_第1张图片

引入

但是,设想这样一个情景,我们想要两个图片依次加载,我们命名了一个新的XHR对象req_new,现在我们有两个xhr对象,第一个加载req,第二个加载req_new,为了实现依次加载这个情景,我们需要在req的onload函数里面调用req_new的send方法,会显得代码十分糟糕!!!代码如下:

const req = new XMLHttpRequest();   //创建XMLHttpRequest对象
const url = "https://c-ssl.duitang.com/uploads/item/202002/24/20200224112148_nknod.jpg" //要渲染的图片链接
const req_new = new XMLHttpRequest();
const img = document.getElementById('img');
const img_new = document.getElementById('img_new');

//初始化req,onload里面调用req_new的send
req.open('GET', url);   //以GET方式请求url资源
req.responseType = 'blob';
req.onload = function () {        //设定一个回调函数
    if (req.status === 200) {
        img.src = url; //设置img标签的src为指定url
        //调用req_new的send
        req_new.send();
    }
    else {
        console.log('failed');
    }
}
req.onerror = function () {
    console.log("network error");
}

//send req同时进入了req的onload并调用req_new的send
req.send();


/**
 * 以下这一段代码,仅负责初始化req_new,若要唤起onload,调用req_new.send()后才可以
 */
req_new.open('GET', url);   //以GET方式请求url资源
req_new.responseType = 'blob';
req_new.onload = function () {        //设定一个回调函数
    if (req_new.status === 200) {
        img_new.src = url; //设置img标签的src为指定url
    }
    else {
        console.log('failed');
    }
}
req_new.onerror = function () {
    console.log("network error");
}

由于onload的逻辑,导致上述代码十分难看,如果有多个xhr会令人很绝望,因此我们引入了promise来解决这一问题!

JS Promise 基础补充

1.箭头函数 =>

(ES6 标准下新增的函数)
相当于匿名函数,甚至{}和return都可以省略

x => x * x

相当于

function (x) {
    return x * x;
}

什么叫匿名函数?

定义如下:

[外部变量访问方式说明符] (参数表) -> 返回值类型
{
语句块
}

外部变量访问方式说明符:

可以是=或&,表示{}中用到的、定义在{}外面的变量在{}中是否允许被改变。=表示不允许,&表示允许。当然,在{}中也可以不使用定义在外面的变量。“-> 返回值类型”可以省略

怎么去理解这一段话呢?
可以理解为,给{ }语句块所引用的内容加上了static,不允许改变值,只能用值

匿名函数(lambda)实例

代码示例:

int a[4] = {11, 2, 33, 4};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );

这段程的输出结果是:
11 2 33 4

程序第 2 行使得数组 a 按个位数从小到大排序。具体的原理是:sort 在执行过程中,需要判断两个元素 x、y 的大小时,会以 x、y 作为参数,调用 Lambda 表达式所代表的函数,并根据返回值来判断 x、y 的大小。这样,就不用专门编写一个函数对象类了。

第 3 行,for_each 的第 3 个参数是一个 Lambda 表达式。for_each 执行过程中会依次以每个元素作为参数调用它,因此每个元素都被输出。

好了,我们大概了解了一下,匿名函数,接下来我们继续看promise吧!!!


2.js运行基础

我们一定要清楚,js语言运行的环境时单线程的,也就是说一次只能完成一个任务,也就是一条流水线,如果有多个任务就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。这与java的多线程环境截然不同,所以要加以区分,之前在b站看视频,有印象有视频提到解决这个问题的方法,好像就是靠异步来解决。

对于异步的理解,可以改变程序执行顺序的操作就可以看成为异步操作

而解决方案有两种,最基础的异步是setTimeout和setInterval函数如一下代码所示

console.log("小明");
setTimeout(function(){
    console.log("小张");
},500);

setTimeout(function(){
    console.log("小刘");
},500);

setTimeout(function(){
    console.log("小王");
},500);

console.log("小高")

输出顺序是,小明,小高,小张,小刘,小王

因此,小明和小高所在的流水线同步了,而setTimeout函数进入了任务队列

任务队列

任务队列是事件队列,IO设备完成一项任务,就在任务事件中添加一个事件(许可),表示相关异步任务可以进入主线程了,主线程读取任务队列,任务队列除了IO设备事件,还有用户鼠标点击,页面滚动等一系列事件。

异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行相应的回调函数。例如ajax的success,complete,error也都指定了各自的回调函数,这些函数就会加入任务队列中,等待执行。

第二种实现异步也就是下面要说的promise,它可以避免层层回调函数嵌套,导致代码很糟糕。
具体是这么说的:

利用promise可以_将异步操作以同步操作的流程表达出来_,避免了层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更加容易。

但注意promise无法取消,一旦建立就会立即执行,无法中途取消。而且,如果不设置回调函数,promise内部抛出的错误不会反映到外部。当处于Pending状态时,无法得知进展到哪一个阶段

ps:啥是pending状态??
百度了一下,pending状态其实就是未决的意思,悬而未决,例如请求已经发送,但是还没有得到回返。


Promise

promise的三种状态:
Pending-promise的初始状态,等到任务完成或是被拒绝;Resolved-执行完成并且成功的状态;Rejected-执行完成并且失败的状态。此三个状态不能相互逆转
then方法:
promise对象必须实现then方法,then是promise的核心!then方法必须返回一个promise对象,在promise对象里,可以注册多个then方法,并且回调的执行顺序跟注册顺序一致哦

ps:then方法接收两个回调函数,他们分别是成功时的回调和失败时的回调

var promise = new Promise(function(resolve, reject){
    if(/*异步任务执行成功*/){
        resolve(value);
    }else{
        reject(error);
    }
});

value代表异步任务执行成功时从promise函数获取的值,可以理解为函数的私有变量,将promise值拿来,在then中应用


修改

现在,我们修改一下最开始我们写的异步代码

function p(img) {

    return new Promise((resolve, reject) => {
        const req = new XMLHttpRequest();   //创建XMLHttpRequest对象
        req.open('GET', url);
        req.responseType = 'blob';
        req.onload = function () {
            if (req.status === 200) {
                img.src = url;
                return resolve(req.response);   //响应成功
            }
            else {
                console.log('failed');
                return reject(Error('failed')); //响应失败
            }
        }
        req.onerror = function () {
            console.log("network error");
            return reject(Error('Network error'));
        }
        req.send();
    })
}
p(img).then((res) => {
    p(img_new);                 //then的神奇之处,如果return p()的话,还可以再接一个then
    console.log(res)
});

then的特点,无限回调,只要你在本次then中又返回了一个promise,你就可以在下一个then中使用本次返回的promise

//稍微改一下调用函数的逻辑
p(img).then((res) => {
    console.log(res)
    return p(img_new);                 //then的神奇之处,如果return p()的话,还可以再接一个then

}).then((res) => {
    console.log(res);
});

无限嫁接

p(img).then((res) => {
    console.log(res)
    return p(img_new);                 //then的神奇之处,如果return p()的话,还可以再接一个then

}).then((res) => {
    console.log(res);
    return p(img2)
}).then((res) => {
    console.log(res);
    return p(img3)
}).then((res) => {
    console.log(res);
    return p(img4)
});

最后加上异常捕捉

p(img).then((res) => {
    console.log(res)
    return p(img_new);                 //then的神奇之处,如果return p()的话,还可以再接一个then

}).then((res) => {
    console.log(res);
    return p(img2)
}).then((res) => {
    console.log(res);
    return p(img3)
}).then((res) => {
    console.log(res);
    return p(img4)
}).catch((err)=>{											//捕捉过程中的全部异常
  console.log(err);
});

参考:
1.CSDN-橡胶米东:《js-promise的用法》
2.Bilibili UP:大翔_Arcxiang:《异步Javascript - 从XMLHttpRequest到Promise》
3.CSDN yangxiaodong88 《=> js 中箭头函数使用总结》
4.jiangzhenwei6 《window.XMLHttpRequest详解》

你可能感兴趣的:(ajax,javascript,服务器,前端)