异步任务顺序数据请求之回调地狱的破解!

2,558 阅读4分钟

前言

 js执行机制

js是一个单线程的执行环境,即一次只能执行一个任务。如果有数个任务,那么这些任务会从上至下依次挨个执行。这种执行方式的好处是简单,实现也简单,缺点是这个如果有大量的任务,或者其中一个任务耗时时间很长,其他的任务暂时无法执行,就会造成浏览器无响应(俗称假死,卡死)。而前端主动去找后端要数据就是一种耗时操作,所以使用了”异步模式。

异步也叫非阻塞模式,浏览器在下载js的同时,还会执行后续的页面处理.

什么时候需要异步:

        1 需要等待的情况

        2 在等待过程中不能像alert一样阻塞程序运行

        3 等待的情况需要异步

异步操作

例如:有三个数据请求,要在异步操作时按照下面顺序进行请求

1:'api.github.com/users/ruany…'

2:'api.github.com/users/ruany…'

3:'api.github.com/search/issu…'

回调地狱的出现

完成'api.github.com/users/ruany…

浏览器向服务器发送请求,服务器相应请求返回数据,浏览器执行异步操作。

首先进行数据请求实例话 xhr ,来做ajax这是一个耗时的任务。

let xhr = new XMLHttpRequest();

由于要实现三个数据请求,所以进行封装ajax的请求,因为要进行耗时操作,使用回调函数

当请求的url数据到了之后,在使用callback函数对url数据进行操作。

function loadAjaxContent(url,callback) {            
    console.log(xhr.readyState,'-------1');           
    xhr.open('GET',url);  // GET 动词  http 三次握手 建立好通信的通道            
    console.log(xhr.readyState,'-------2'); 


           
    xhr.onreadystatechange = function() {                
        console.log(xhr.readyState,'-------4');                
        if(xhr.readyState ==4 && xhr.status ==200) {                    
            callback(JSON.parse(xhr.responseText));               
         }                
    }  
         
     xhr.send();            
    console.log(xhr.readyState,'-------3');        
}

分析上面代码:

1. 把'http://api.github.com/users/ruanyf'上的数据拿到要经过以下几个步骤:

  • xhr.open('GET',url); 打开一个请求通道,传一个谓语动词“GET,以明文的方式发送一个网络请求,js自主发送请求能力。

  • xhr.send(); 发送请求

  • xhr.onreadystatechange{}监听,然后返回请求的数据

2. xhr.onreadystatechange是 事件监听的方式

3. if(xhr.readyState ==4 && xhr.status ==200)当xhr.readyState为4表示响应已完成,但是也可以为失败的完成(服务器中出现语法错误或404),所以加一个状态码的判断。

4. callback(xhr.responseText);由于在xhr(ie6的时代)时代没有发明json,但是json诞生后发现:

json  {login:'ruanyf'} 作为数据传输的格式,

比xml 更轻量 <userInfo><login>ruanyf</login><userInfo>

所以将callback(xhr.responseText)改为callback(JSON.parse(xhr.responseText));

5. 调用函数loadAjaxContent()的执行结果:

loadAjaxContent('http://api.github.com/users/ruanyf',(users) => {            
    console.log(users,'users');                 
});

                                     

xhr.readyState 有哪几个值

  • 0  -(未初始化)还没有调用send()方法

  • 1 -(载入)已调用send()方法,正在发送请求

  • 2 -  请求已发送, 正在处理中 pending 服务器正在做一些运算

  • 3 -  请求在处理中, 已有部分数据, 将大的数据包进行分片切割  断点续传

  • 4 -(完成)响应内容解析完成,可以获取并使用服务器的响应了

回调地狱的出现----完成三个数据请求的顺序执行

loadAjaxContent('http://api.github.com/users/ruanyf',(users) => {            
    console.log(users,'users');           
    loadAjaxContent('http://api.github.com/users/ruanyf/repos',(repos) => {               
            console.log(repos,'repos') ;                
            loadAjaxContent('http://api.github.com/search/issues?q=ruanyf',(results) => {                   
                console.log(results,'results') ;                
            });           
     });       
});

执行结果:

                   

成功顺序的执行了数据请求。但是如果有10个数据请求的话,会变成

回调地狱

回调地狱的解决

使用Promise

Promise 函数是立即执行的,Promise是包一些ajax这样的耗时任务,当它执行完之后, 其中resolve,会继续执行下去。resolve reject 就是 交出执行的控制权。当使用Promise

<script>        
    let xhr = new XMLHttpRequest();        
    let p1 = new Promise((resolve,reject) => {            
    console.log('bbbb'); // Promise 函数是立即执行的 
    xhr.open('GET','http://api.github.com/users/ruanyf');          
    xhr.onreadystatechange = function() {                
        if(xhr.readyState ==4 && xhr.status ==200) {                    
        resolve(JSON.parse(xhr.responseText));                 
            }            
        }            
    xhr.send();        
    })   


    
    p1            
    .then(data => {                // users                
        console.log('uses Promise',data);           
     })   

</script>

先执行let p1这请求,然后做.then()中的内容。

实现了数据的输出:

                       

使用fetch

使用fetch,"GET"请求直接fetch('api.github.com/users/ruany… 如图:

                                          

其中对应xhr的值为2(请求已发送, 正在处理中 pending 服务器正在做一些运算)。因为返回的Promise,所以可以使用.then。

let xhr = new XMLHttpRequest();        
fetch('http://api.github.com/users/ruanyf')           
 .then(data => {               
     console.log('user',data)          
 })

直接打印data,返回的是readstream  二进制流利用data.json()转化为json对象,这也要花时间。

                                 

完成三个数据请求:

let xhr = new XMLHttpRequest();          
fetch('http://api.github.com/users/ruanyf')        // promise           
 .then(data => data.json())   //readstream 二进制流 json化  变成json对象 也是要花时间的       
 .then(data => {                
    console.log('user',data)                
    return fetch('http://api.github.com/users/ruanyf/repos')           
 .then(data => data.json())           
 .then(data => {               
     console.log('repos',data)               
     return fetch('http://api.github.com/search/issues?q=ruanyf')            
    })            
.then(data => data.json())            
.then(data => {                
    console.log('results',data);           
     })          
  })

在then回调函数中, 使用return promise实例 ,可以继续then able。

现在只有一层,看起来是同步的,不会有嵌套函数,不会再出现回调地狱。

总结

在控制多个异步任务的按顺序执行,回调函数会出现嵌套,一直嵌套一直嵌套,出现了回调地狱使用fetch内置的.then,只有一层函数。