1. 程式人生 > 實用技巧 >js處理非同步的幾種方式

js處理非同步的幾種方式

同步、非同步

1.造成原因:js是單執行緒的語言

2.概念:

  • 同步任務:實時處理 在主執行緒上排隊執行的任務,按照先後順序執行
  • 非同步任務:分時處理 不進入主執行緒、而進入"任務佇列"的任務,只有等主執行緒任務執行完畢,"任務佇列"開始通知主執行緒,請求執行任務,該任務才會進入主執行緒執行
  • 非同步模式可以一起執行多個任務

3.js處理非同步的幾種方式

1. 底層原理

XML 的英文全稱是 EXtensible Markup Language,即可擴充套件標記語言

1.1 使用xhr發起GET請求

// 1. 建立 XHR 物件
var xhr = new XMLHttpRequest()
// 2. 呼叫 open 函式
xhr.open('GET', 'http://www.shanshan.top:3006/api/getlist?id=1&list=統計') //?id=1查詢字串
// 3. 呼叫 send 函式
xhr.send()
// 4. 監聽 onreadystatechange 事件
xhr.onreadystatechange = function () {
    當 readyState 等於 4 且狀態為 200 時,表示響應已就緒:
  if (xhr.readyState === 4 && xhr.status === 200) {
     // 獲取伺服器響應的資料
     console.log(xhr.responseText)
   }
}

1.2 使用xhr發起POST請求

// 1. 建立 xhr 物件
var xhr = new XMLHttpRequest()
// 2. 呼叫 open 函式
xhr.open('POST', 'http://www.shanshan.top:3006/api/addlist')
// 3. 設定 Content-Type 屬性(固定寫法)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// 4. 呼叫 send 函式
xhr.send('bookname=三毛&author=張三&publisher=本地圖書出版社')
// 5. 監聽事件
//onreadystatechange 儲存函式,每當 readyState 屬性改變時,就會呼叫該函式
xhr.onreadystatechange = function () {
   // 物件的 readyState 屬性,用來表示當前 Ajax 請求所處的狀態 
   // 當 readyState 等於 4 且狀態為 200 時,表示響應已就緒:
  if (xhr.readyState === 4 && xhr.status === 200) {
      // 獲取伺服器響應的資料
    console.log(xhr.responseText)
  }
}

1.3 FormData物件管理表單資料

Ajax 操作往往用來提交表單資料。為了方便表單處理,HTML5 新增了一個 FormData 物件,可以模擬表單操作:

 // 1. 新建 FormData 物件
 var fd = new FormData()
 // 2. 為 FormData 新增表單項
 fd.append('uname', 'zs')
 fd.append('upwd', '123456')
 // 3. 建立 XHR 物件
 var xhr = new XMLHttpRequest()
 // 4. 指定請求型別與URL地址
 xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
 // 5. 直接提交 FormData 物件,這與提交網頁表單的效果,完全一樣
 xhr.send(fd)

FormData物件也可以用來獲取網頁表單的值,示例程式碼如下:

// 獲取表單元素
var form = document.querySelector('#form1')
// 監聽表單元素的 submit 事件
form.addEventListener('submit', function(e) {
 e.preventDefault()
 // 根據 form 表單建立 FormData 物件,會自動將表單資料填充到 FormData 物件中
 var fd = new FormData(form)
 var xhr = new XMLHttpRequest()
 xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
 xhr.send(fd)
 xhr.onreadystatechange = function() {}
})

2. 原生ajax

2.1 jQuery中的Ajax

$.ajax({
    type:'GET',    //請求方式
    url:'http://www.shanshan.top:3006/api/getlist',   //請求的url地址
    data:{id:1},   //這次請求需攜帶的引數
    success:function(res){    //請求成功之後的回撥函式
        console.log(res)      //res 後臺返回的資料
    }
})

$.ajax({
    type:'POST',
    url:'http://www.shanshan.top:3006/api/addlist',
    data:{
        name:'張三',
        sex:'男'
    },
    success:function(res){
        console.log(res)
    }
})

2.2 通過Ajax提交表單資料

<from id="from1">
        <input type="text" name="username">
        <input type="password" name="password">
        <button type="submit">提交</button>
</from>

<script>
   $('#from1').submit(function (e) {
      e.preventDefault() //阻止表單的預設提交和頁面的跳轉
       var data = $(this).serialize() //一次性獲取表單的資料,必須為每個表單元素新增 name 屬性
    })
</script>

多次呼叫ajax時,不是按照書寫ajax程式碼的順序返回結果,如果存在依賴關係就需要巢狀,會造成回撥地獄

 $.ajax({
      url: 'http://localhost:3000/data',
      success: function(data) {
        console.log(data)
        $.ajax({
          url: 'http://localhost:3000/data1',
          success: function(data) {
            console.log(data)
            $.ajax({
              url: 'http://localhost:3000/data2',
              success: function(data) {
                console.log(data)
              }
            });
          }
        });
      }
    });

3. promise物件主要解決非同步深層巢狀造成回撥地獄的問題--es6

promise物件是一個建構函式,用來生成Promise例項

3.1 基本用法

function timeout() {
     var p = new Promise(function(resolve,reject){
     //非同步操作
        setTimeout(function(){
        var flag = false;
        if(flag) {
          //3.失敗時呼叫resolve()
          resolve('hello');
        }else{
          //4.成功是呼叫reject()
          reject('出錯了');
        }
      }, 100);
      });
     return p
}
//p.then獲取處理結果
p.then(function(ret){
  從resolve得到的正常結果
},function(ret){
   從reject得到的錯誤資訊
})

3.2 基於Promise傳送Ajax請求

 <script type="text/javascript">
    /*
      基於Promise傳送Ajax請求
    */
    function queryData(url) {
     #   1.1 建立一個Promise例項
      var p = new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            # 1.2 處理正常的情況
            resolve(xhr.responseText);
          }else{
            # 1.3 處理異常情況
            reject('伺服器錯誤');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
      return p;
    }
    
    #傳送多個ajax請求並且保證順序

    多個ajax任務通過 .then 的方式變成了線性關係,保證的執行的順序
    queryData('http://localhost:3000/data')
      .then(function(data){
        console.log(data)
        //return的是一個新的Promise物件,下一個then的呼叫者就是上一個return出來的Promise物件
        return queryData('http://localhost:3000/data1'); 
      })
      .then(function(data){     //data接收上一個非同步任務返回的結果
        console.log(data);
        return queryData('http://localhost:3000/data2');
      })
      .then(function(data){     //p.then()得到非同步任務的正確結果  resolve中的資訊
        console.log(data)
      })
      .catch(function(data){    //p.catch()獲取異常資訊  reject中的資訊
        console.log(data)
      })
      .finally(function(){  	//p.finally()成功與否都會執行(不是正式標準
      	console.log("成功與否都會執行")
      })
  </script>
  • 在then方法中函式的返回值,你也可以直接return資料而不是Promise物件,在後面的then中就可以接收到資料了

3.3 Promise常用的API

1.例項方法

  • p.then ( ) 得到非同步任務的正確結果 resolve中的資訊
  • p.catch ( ) 獲取異常資訊 reject中的資訊
  • p.finally ( ) 成功與否都會執行(不是正式標準)

2.物件方法

.all()

  • Promise.all方法接受一個數組作引數,陣列中的物件(p1、p2、p3)均為promise例項(如果不是一個promise,該項會被用Promise.resolve轉換為一個promise)。它的狀態由這三個promise例項決定

  • 併發處理多個非同步任務,所有任務都執行完成才能得到結果

.race()

  • Promise.race方法同樣接受一個數組作引數。當p1, p2, p3中有一個例項的狀態發生改變(變為fulfilledrejected),p的狀態就跟著改變。並把第一個改變狀態的promise的返回值,傳給p的回撥函式

  • 併發處理多個非同步任務,只要有一個任務完成就能得到結果

    
    function queryData(url) {
     #   1.1 建立一個Promise例項
      var p = new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            # 1.2 處理正常的情況
            resolve(xhr.responseText);
          }else{
            # 1.3 處理異常情況
            reject('伺服器錯誤');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
      return p;
    }
    
/*
   Promise常用API-物件方法
*/  
var p1 = queryData('http://localhost:3000/a1');
var p2 = queryData('http://localhost:3000/a2');
var p3 = queryData('http://localhost:3000/a3');

Promise.all([p1,p2,p3]).then(function(result){
 //all 中的引數  [p1,p2,p3]   和 返回的結果一 一對‘應["TOM", "JERRY", "SPIKE"]
   console.log(result) //["TOM", "JERRY", "SPIKE"]
})

Promise.race([p1,p2,p3]).then(function(result){
 // 由於p1執行較快,Promise的then()將獲得結果'P1'。   p2,p3仍在繼續執行,但執行結果將被丟棄。
 console.log(result) // "TOM"
})

4.介面呼叫---fetch --- xhr的升級版

fetch不是ajax的進一步封裝,而是原生js,沒有使用XMLHttpRequest物件

4.1 基本用法
  <script type="text/javascript">
    /*
      Fetch API 基本用法    fetch(url).then()
     	第一個引數請求的路徑   Fetch會返回Promise   所以我們可以使用then 拿到請求成功的結果 
    */
    fetch('http://localhost:3000/fdata').then(function(data){
      // text()方法屬於fetchAPI的一部分,它返回一個Promise例項物件,用於獲取後臺返回的資料
      return data.text();
    }).then(function(data){
      //   在這個then裡面我們能拿到最終的資料  
      console.log(data);
    })
  </script>

4.2 fetch API 中的 HTTP 請求

1 GET引數傳遞 - 傳統URL 通過url ? 的形式傳參

fetch('http://localhost:3000/books?id=123', {
            	# get 請求可以省略不寫 預設的是GET 
                method: 'get'
            })
            .then(function(data) {
            	# 它返回一個Promise例項物件,用於獲取後臺返回的資料
                return data.text();
            }).then(function(data) {
            	# 在這個then裡面我們能拿到最終的資料  
                console.log(data)
            });

2 GET引數傳遞 restful形式的URL 通過/ 的形式傳遞引數 即 id = 456 和id後臺的配置有關

fetch('http://localhost:3000/books/456', {
            	# get 請求可以省略不寫 預設的是GET 
                method: 'get'
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

3 DELETE請求方式引數傳遞 刪除id 是 id=789

fetch('http://localhost:3000/books/456', {
            	# get 請求可以省略不寫 預設的是GET 
                method: 'get'
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

4 POST請求傳參

fetch('http://localhost:3000/books', {
                method: 'post',
            	# 3.1  傳遞資料 
                body: 'uname=lisi&pwd=123',
            	#  3.2  設定請求頭 
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });


fetch('http://localhost:3000/books', {
                method: 'post',
                body: JSON.stringify({
                    uname: '張三',
                    pwd: '456'
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
       })
       .then(function(data) {
           return data.text();
       }).then(function(data) {
           console.log(data)
       });

5 PUT請求傳參 修改id 是 123 的

fetch('http://localhost:3000/books/123', {
                method: 'put',
                body: JSON.stringify({
                    uname: '張三',
                    pwd: '789'
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

fetchAPI 中 響應格式

fetch('http://localhost:3000/json').then(function(data){
  // return data.json();   //  將獲取到的資料使用 json 轉換物件
  return data.text(); //  //  將獲取到的資料 轉換成字串 
}).then(function(data){
   // console.log(data.uname)
   // console.log(typeof data)  string
   var obj = JSON.parse(data); //轉化成物件
   console.log(obj.uname,obj.age,obj.gender)
})

5.介面呼叫--axios

Axios 是一個基於 promise 的 HTTP 庫,是專注於網路資料請求的庫,可以用在瀏覽器和 node.js 中。

5.1 綜合寫法

axios({
	method:"請求的方式"
	url:"請求的地址"
	data:傳送的資料
	headers:請求頭
}).then(()=>{})
.catch(()=>{})

5.2 axios 全域性配置

#  配置公共的請求頭 
axios.defaults.baseURL = 'https://api.example.com';
#  配置 超時時間
axios.defaults.timeout = 2500;
#  配置公共的請求頭
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
# 配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

5.3 axios 攔截器

5.3.1 請求攔截器

  • axios.interceptors.request.use(function(config) {
          console.log(config.url)
          # 1.1  任何請求都會經過這一步   在傳送請求之前做些什麼   
          config.headers.mytoken = 'nihao';
          # 1.2  這裡一定要return   否則配置不成功  
          return config;
        }, function(err){
           #1.3 對請求錯誤做點什麼    
          console.log(err)
        })
    

5.3.2 響應攔截器

  • axios.interceptors.response.use(function(res) {
          #2.1  在接收響應做些什麼  
          var data = res.data;
          return data;
        }, function(err){
          #2.2 對響應錯誤做點什麼  
          console.log(err)
        })
    

5.4 建立axios例項

  • const instance = axios.create({
      baseURL: 'https://some-domain.com/api/',
      timeout: 1000,
      headers: {'X-Custom-Header': 'foobar'}
    });
    

6. **async await **讓非同步程式碼看起來更像同步程式碼 --ES7

async關鍵字 放到函式前面 (返回值是一個promise例項物件)

await關鍵字 只能在使用async定義的函式中使用(得到非同步的結果,await只能得到成功的結果,如果想要得到失敗的結果,只能用trycatch)

6.1處理單個非同步請求

async getFaceResult () {
//用的是try/catch來捕獲異常,把await放到try中進行執行
//如有異常,就使用catch進行處理,不會影響後面的程式碼
      try {
         let location = await getLocation()
      } catch(err) {
         console.log("錯誤",err)
    }
}

6.2處理多個非同步請求

async getFaceResult () {
      try {
          // 新增await之後 當前的await 返回結果之後才會執行後面的程式碼
         let location = await getLocation()
         let ret = await getList()
      } catch(err) {
         console.log("錯誤",err)
    }
}