由ajax引起的...

182 阅读10分钟

为什么会有异步

最开始接触异步的概念是学习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里的回调

清风至,白露生