为什么会有异步
最开始接触异步的概念是学习ajax请求的时候,请求并不会立即得到响应,需要设置一个回调函数。待响应返回后,接收处理。
这是因为在js的世界里,代码执行是单线程的,即一次只能执行一个任务,任务需要排队去执行。这会造成一些问题。比如发送一个网络请求,是同步的,请求时间长,这时由于后面的任务要等待请求完成才能执行,什么都做不了,页面会出现假死的情况。但实际上我们发送网络请求都是异步的。
为了解决该问题,任务就分成了两种类型,同步的和异步的。js里常见的异步任务有ajax,setTimeout,setInterval
常见的异步解决方案有回调callback,promise,async/await
ajax
根据上面的我们可以得知,如果网络请求是同步的,无法得知具体的网络情况如何,很容易出现浏览器假死的状态。所以js里使用ajax去进行异步的网络请求。所以ajax是异步的。
ajax:asynchronous JavaScript and xml 即异步的js和xml。XMLHttpRequest对象是ajax里重要的概念
使用ajax:
let req = new XMLHttpRequest();
req.onreadystatechange = ()=>{//状态发生改变时,执行回调函数
if(req.readyState===4){//请求成功
//判断响应结果
if(req.status===200){
*******************************
//成功,可以通过responseText拿到响应的文本
return req.responseText;
//也可以使用回调函数去处理,如下
//return success(req.responseText);
}else{
*******************************
//失败,根据响应码判断失败原因
return req.status;
//也可以使用回调函数去处理,,如下
//return fail(req.status);
}
}else{
//HTTP请求还继续
}
};
req.open('get',url);
rep.send();
上面星号后面就是当异步的网络请求成功或失败后进行操作。我们可以定义success和fail两个回调函数去处理。
跨域
接下来就涉及到ajax发送网络请求必不可少的问题:跨域。
什么是跨域:由于浏览器的同源策略,js发送ajax请求的时候,发送请求的url要和当前页面完全一致。同源指的是域名,协议,端口都相同。解决方案:
》1:cors:只要在请求得到的响应头Access-Control-Allow-Origin里设当前请求的域名就可以了。使用cros,其实只要对方服务器设置一下Access-Control-Allow-Origin即可。
》2:jsonp:jsonp就是json with padding。它是利用浏览器允许跨域引用js资源。什么意思,就是比如我们用一个script标签,里面的src属性,可以写一个url去获取到相应的资源,而该处的url就没有同源策略的限制。还有img标签,也是如此。 通常jsonp请求会以函数调用的形式返回。我们事先在页面中准备好函数,然后给页面动态加一个script标签,相当于动态读取外域的js资源,最后就等着接收回调了。
promise
js的所有代码都是单线程执行的,而js的所有网络操作,浏览器事件都必须异步执行。以前常用的异步解决方案有轮询,回调函数,事件。上面的ajax异步网络请求,回调函数success和fail就是在异步网络请求成功或失败后被调用执行。但都有很多弊端,比如说回调函数会导致回调地狱,回调函数命名不规范等问题。
Primise也是异步编程的一种解决方案。它提供了一个统一的处理异步操作的规范,一系列的接口。
Promise有三种状态,Pending初始态,Fulfilled成功态,Rejected失败态
Promise的特点:1>对象状态不受外部影响。Promise对象代表一个异步操作。它的状态只受异步操作结果的影响;2>一旦状态改变,就不会再变,任何时候都可以得到这个结果。缺点:1>无法取消;2>不设回调函数的话,Promise内部抛出的错误,不会反应到外部。3>当处于pending状态时,不知道目前进展到哪一阶段(刚开始还是即将完成)
Promise是个对象,接收一个函数类型的参数。而该函数参数又接收两个函数类型参数。第一个参数是当异步操作成功后的回调函数,第二个参数是异步操作失败后的回调函数。Promise对象返回一个实例,通过实例的then方法去设置具体的回调函数。
promise的用法:
let promise = new Promise((resolve,reject)=>{
if(//异步操作成功){
resolve()
}
else{
//异步操作失败
reject()
}
})
promise.then((res)=>{
//当异步操作成功,处理,参数res是上面resolve里的参数,视具体情况而定
return res;
},(err)=>{
//当异步操作失败,处理,参数err是上面reject里的参数,视具体情况而定
return err;
});
在ajax里面使用promise,
let promise = new Promise((resolve,reject)=>{
let req = new XMLHttpRequest();
req.onreadystatechange = ()=>{//状态发生改变时,执行回调函数
if(req.readyState===4){//请求成功
//判断响应结果
if(req.status===200){
*******************************
//成功,可以通过responseText拿到响应的文本
resolve(req.responseText);
}else{
*******************************
//失败,根据响应码判断失败原因
reject(req.status);
}
}else{
//HTTP请求还继续
}
};
req.open('get',url);
rep.send();
})
promise.then((res)=>{
console.log(res)
},(err)=>{
console.log(err)
});
链式操作,返回的还是promise对象
Promise.all(),当所有promise对象都变为fulfilled,才会执行resolve。只要有一个是rejected状态,Promise.all()返回的对象状态就也是rejected。另当其中一个对象是rejected状态,并且有自己的catch方法,那么是不会调用Promise.all()的catch方法。
Promise.race(),同样将多个Promise实例,包装成一个新的Promise实例。只要其中任意一个实例率先改变状态,p的状态就跟着改变。
Promise.allSettled(),只有所有参数实例都返回结果,包装实例才会结束。
Promise.any(),只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态。
Promise.prototype.finally()方法用于指定不管Promise对象最后状态如何,都会执行的操作
手写promise:
function Promise(excutor){
let self = this
self.state = 'pending'
self.result = undefined
self.error = undefined
self.callbackResult = [];
self.callbackError = [];
function resolve(result){
// pending->fulfilled 按照成功清单执行
if(self.state==='pending'){
self.state = 'fulfilled'
self.result = result
self.callbackError.forEach((fn) => {
fn();
});
}
}
function reject(error){
// pending->rejected 按照异常清单执行
if(self.state==='pending'){
self.state = 'fulfilled'
self.error = error
self.callbackError.forEach((fn) => {
fn();
});
}
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this//上面不要写成箭头函数了,箭头函数没有this
if(self.state==='fulfilled'){
onFulfilled(self.result)
}
if(self.state==='rejected'){
onRejected(self.error)
}
//若为异步的操作
if (self.state === "pending") {
self.callbackResult.push(function () {
onFulfilled(self.result);
});
self.callbackError.push(function () {
onRejected(self.error);
});
}
}
测试代码:
let Promise = require("./promise.js");//引入Promise,具体路径视情况
let promiseTest = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(45);
}, 5000);
});
promiseTest.then(
(data) => {
console.log("第一个then方法》成功:", data);
},
(data) => {
console.log("第一个then方法》失败:", data);
}
);
promiseTest.then(
(data) => {
console.log("第二个then方法》成功:", data);
},
(data) => {
console.log("第二个then方法》失败:", data);
}
);
Promise的链式调用和值穿透特性!!!Promise可以链式调用,就在于then种return了一个新的promise。那么如何实现呢?需要增加一个resolvePromise的方法。同时then方法也需要进行改进,then会调用该方法去处理。
function resolvePromise(promise2,x,resolve,reject){
// 循环引用报错
if(promise2===x){
reject(new TypeError("promise2和x不能相同"))
}
if(x && (typeof x === "function" || typeof x === "object")){
// 防止多次调用
let called
try{
let then = x.then
// 如果then是函数,就默认是promise了
if(typeof then === "function"){
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调,called的用途:成功和失败只能调用一个
then.call(x,result=>{
if(called) return
called = true
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2,result,resolve,reject)
},error=>{
if(called) return
called = true
// 失败了
reject(error)
})
}else{
// 直接成功即可
resolve(x)
}
}catch(error){
// 取then出错了,就不要继续执行了
if(called) return
called = true
reject(error)
}
}else{
resolve(x)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === "function" ? onFulfilled:value=>value
// onRejected如果不是函数,就忽略onRejected,扔出错误
onRejected = typeof onRejected === "function" ? onRejected : error=>{throw error}
let promise2 = new Promise((resolve,reject)=>{
// 异步解决:
if(self.state==='fulfilled'){
setTimeout(()=>{
try{
let x = onFulfilled(self.result)
resolvePromise(promise2,x,resolve,reject)
}catch(error){
reject(error)
}
})
}
if(self.state==='rejected'){
setTimeout(()=>{
try{
let x = onRejected(self.error)
resolvePromise(promise2,x,resolve,reject)
}catch(error){
reject(error)
}
})
}
if (self.state === "pending") {
self.callbackResult.push(function () {
setTimeout(()=>{
try{
let x = onFulfilled(self.result)
resolvePromise(promise2,x,resolve,reject)
}catch(error){
reject(error)
}
})
});
self.callbackError.push(function () {
setTimeout(()=>{
try{
let x = onRejected(self.error)
resolvePromise(promise2,x,resolve,reject)
}catch(error){
reject(error)
}
})
});
}
})
return promise2
}
手写Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 可能是普通值
Promise.resolve(promises[i]).then((data) => {
processValue(i, data);
}, (err) => {
reject(err);
return;
});
}
}
});
}
手写Promise.race
Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。如果传的参数数组是空,则返回的 promise 将永远等待。如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
Promise.race = function (promises) {
promises = Array.from(promises);//将可迭代对象转换为数组
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((data) => {
resolve(data);
return;
}, (err) => {
reject(err);
return;
});
}
}
});
}
axios
基于上面的了解,我们想要发送网络请求,又想异步处理能够简单点,axios完美的解决了我们的需求。
axios是一个基于promise的HTTP库,可以用在浏览器和node.js中。
具体使用的时候,根据实际场景可以对axios进行封装。像下面所示,新建一个service.js的文件,
//引入axios
import axios from 'axios'
//相关配置
const config = {
headers: {
'Content-Type': 'application/json'
}
}
//创建axios实例
const service = axios.create(config)
//请求拦截
service.interceptors.request.use(
config => {
// Do something before request is sent
return config
},
error => {
// Do something with request error
return Promise.reject(error)
}
)
//响应拦截
service.interceptors.response.use(
response => {
// Do something with response data
return response.data
},
error => {
// Do something with response error
return Promise.reject(error)
}
)
export default service
接下来使用的时候,可以引入service。
async/await
异步编程的最高境界,就是根本不用关心它是不是异步。通过使用async、await,异步代码看起来更像是老式同步代码。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
async函数可以看为多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
async函数返回的是一个Promise对象,可以后面跟then函数进行处理。async函数return的返回值,将作为then方法回调函数里的参数。
await一般后面跟Promise对象,返回该对象的值,如果不是Promise对象,就直接返回对应的值。如果用于普通的函数会出错,另外如果想让操作并发执行,可以使用Promise.all或循环。
另外我们可以利用await去实现休眠效果,如下所示:
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
// 用法
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}
one2FiveInAsync();
阮老师es6里面关于async和await讲解得很清楚。另外关于async和promise的区别,可以参考如下参考文
碎碎念
回调<并不是所有的回调都是异步>如数组的forEach方法。
什么是回调?简单来讲:回调是在另一个函数执行完成之后再被执行的函数。--因此它的名字叫回调;更深入来讲,在JavaScript中,函数是一个对象。正因为如此,函数可以作为函数的参数出现,而且可以作为结果值被返回。这种函数称为高阶函数(higher-order functions)。任何可以作为参数被传递的函数称为回调函数。js里的回调
清风至,白露生