JS——異步編程的六種解決方案


將需要在異步任務后執行的操作,作為參數傳入到異步操作中,當異步操作執行完成后,調用該參數執行后面的操作

ajax(url,()=>{})

回調函數簡單,容易理解和實現;但回調函數的缺點就是,容易寫出回調地獄

多個異步操作需要規定執行順序時產生回調地獄

回調地獄導致代碼不容易閱讀和維護,各個部分高度耦合,使得程序結構混亂,流程那一追蹤

ajax(url,()=>{
    //處理邏輯
    ajax(url1,()=>{
        //處理邏輯
        aja(url2,()=>{
            //處理邏輯
        })
    })
})
  • 事件監聽

    異步任務的執行不取決於代碼的順序,而是取決於某個事件是否發生,事件驅動

    f1.on('done',f2)
    //f1 綁定事件 done,只有當f1發生done事件的時候才執行f2
    function f1(){
        setTimeout(function(){
            //……
            f1.trigger('done')	//觸發done事件,f2立即執行
        }, 1000)
    }
    

    這種方式容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,而且可以“去耦合”,有利於實現模塊化。缺點就是整個程序都會變成事件驅動型,運行流程變得很不清晰

  • 發布訂閱

    假定存在一個"信號中心",某個任務執行完成,就想信號中心"發布(publish)"一個信號,其他任務可以向信號中心"訂閱(subscribe)"這個信號,從而知道什么時候自己可以執行。這就是發布訂閱模式,又稱"觀察者模式"

    jQuery.subscribe('done',f2)
    //f2 向訂閱中心訂閱了 done 信號
    function f1(){
        setTimeout(function(){
            //……
            jQuery.publish('done')	//執行完成后向訂閱中心發布done信號
        },1000)
    }
    
    jQuery.unsubscribe('done',f2)	//f2執行完成后可以取消訂閱
    

    這種方式與"事件監聽類似",但明顯后者優於前者。因為可以通過查看"消息中心",了解存在多少信號,每個信號有多少訂閱者,從而監聽程序的運行

  • Promise

    ES6引入的異步編程解決方案

    graph LR a(回調函數)-- 復雜異步問題 -->b(回調地獄) b-->c(代碼難以閱讀和維護) c-->d(Promise)
    /*##回調函數異步解決方案*/
    setTimeout(()=>{
        console.log('hello world');
        setTimeout(()=>{
            console.log('hello vueJs');
            setTimeout(()=>{
                console.log('hello Promise');   //回調函數產生的回調地獄
            },1000)
        },1000)
    }, 1000)
    /*##Promise異步解決方案*/
    new Promise((resolve, reject)=>{
        //第一次異步請求
        setTimeout(()=>{
            //成功的時候調用resolve
            resolve('hello world')   //通過resolve函數將結果返回到外部 then 處理
        }, 1000)
    }).then((data)=>{
        //處理第一次異步請求
        console.log(data);
        //第二次異步請求
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve()
            },1000)
        })
    }).then(()=>{
        //處理第二次異步請求
        console.log('hello vueJs');
        //第三次請求
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve()
            },1000)
        })
    }).then(()=>{
        //處理第三次異步請求
        console.log('hello Promise');
    })
    
    
    • Promise 怎么使用

      1. 將異步操作放進 Promise 中

      2. new Promise(回調函數)

        new -> 構造函數(1.保存一些狀態信息,2.執行傳入的參數(即回調函數))

      3. 回調函數:(resolve,reject)=>{異步操作}

      4. resolve 和 reject 本身又是函數

      5. 通過 resolve 和 reject 將數據返回到外部 .then() .catch處理 --> 鏈式編程

        可以正確和錯誤的回調都在 then() 中處理

        new Promise((resolve,reject)=>{
            setTimeout(()=>{
                //遇到錯誤調用reject,將錯誤返回到外部 catch 處理
                reject('hello error')
            },1000)
        }).then(data=>{
            console.log(data);
        },err=>{
            console.log(err);
        })
        
    • Promise 的三種狀態

      pending: 等待狀態,比如正在進行的網絡請求,或定時器沒有到時間
      fulfill: 滿足狀態,主動回調 resolve 時,就處於滿足狀態,並且回調 .then()
      reject: 拒絕狀態,主動回調 reject 時,就處於拒絕狀態,並且回調 .catch()

    • Promise的鏈式調用

      graph LR a(返回新的Promise對象)-->b(返回Promise.resolve) b-->c(直接返回數據)
      /*最開始的鏈式調用*/
      new Promise((resolve,reject)=>{
          setTimeout(()=>{
              resolve('hello1')
          })
      }).then((data)=>{
          console.log(data,"第一層數據處理");
          return new Promise(resolve => {
              resolve(data+'1')
          })
      }).then(data=>{
          console.log(data,'第二層數據處理');
          return new Promise(resolve => {
              resolve(data+'1')
          })
      }).then(data=>{
          console.log(data,'第三層數據處理')
      })
      
      
      /*簡化new的鏈式調用*/
      new Promise((resolve,reject)=>{
          setTimeout(()=>{
              resolve('hello1')
          })
      }).then((data)=>{
          console.log(data,"第一層數據處理");
          // return Promise.resolve(data+'1')
          return Promise.reject('error')  //拋出錯誤信息的簡化方式
      }).then(data=>{
          console.log(data,'第二層數據處理');
          return Promise.resolve(data+'1')
      }).then(data=>{
          console.log(data,'第三層數據處理')
      }).catch(err=>{
          console.log(err);
      })
      
      
      /*最終簡化版*/
      new Promise((resolve,reject)=>{
          setTimeout(()=>{
              resolve('hello1')
          })
      }).then((data)=>{
          console.log(data,"第一層數據處理");
          // return data+'1'
          throw 'error'   //拋出錯誤的簡寫方式
      }).then(data=>{
          console.log(data,'第二層數據處理');
          return data+'1'
      }).then(data=>{
          console.log(data,'第三層數據處理')
      }).catch(err=>{
          console.log(err);
      })
      
    • Promise.all

      當一個任務需要多個異步任務的結果才能執行時

      /*1. 通過回調函數實現*/
      let isResult1 = false
      let isResult2 = false
      $ajax({
          url: '',
          success: function () {
              console.log('結果1');
              isResult1 = true
              handleResult()
          }
      })
      $ajax({
          url: '',
          success: function () {
              console.log('結果2');
              isResult2 = true
              handleResult()
          }
      })
      function handleResult() {
          if(isResult1 && isResult2){
              //邏輯處理
          }
      }
      
      /*2. Promise的all方法,同時進行多個異步操作*/
      Promise.all([
          new Promise((resolve,reject)=>{
              $ajax({
                  url: 'url1',
                  success: function (data) {
                      resolve(data)
                  }
              })
          }),
          new Promise((resolve,reject)=>{
              $ajax({
                  url: 'url2',
                  success: function (data) {
                      resolve(data)
                  }
              })
          }),
      ]).then(results=>{
          results[0]  //第一個異步操作返回的結果
          results[1]  //第二個異步操作返回的結果
      })
      
  • 生成器 Generator/yield

    Generator與函數很像,但定義時多個 * 號,並且除了 return 以外,還可以用 yield 返回多次

    function* foo(x){
        yield x+1
        yield x+2
        return x+3
    }
    let f = foo(3);
    f.next()    //{value: 4, done: false}
    f.next()    //{value: 5, done: false}
    f.next()    //{value: 6, done: true}
    f.next()    //{value: undefined, done: true}
    //也可以直接用循環地跌 generator 對象
    for(var x fo foo(3)){
        console.log(x)
    }
    

    generator 函數的特性很適合用來處理異步操作因為它的每一步yield都是按順序的

    function *fetch() {
        yield ajax(url, () => {})
        yield ajax(url1, () => {})
        yield ajax(url2, () => {})
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()
    
  • Aysnc/Await

    Aysnc 函數是 Generator 的優化

    Aysnc 是基於 Promise 實現的,Aysnc 返回一個 Promise 對象

    • Aysnc 對 Generator 的改進

      1. 內置執行器

        Generator 函數執行必須依靠執行器,所以才有了 co 函數庫,而 aysnc 函數只帶執行器。也就是說 async 函數的執行與普通函數的執行一模一樣,只要一行

      2. 更廣適用性

        yield 命令后面只能是 Thunk 函數或者 Promise 對象,而 async 函數的 await 命令后面,可以跟 Promise 對象和 原始類型的值(數值,字符串和布爾值,但這時等同於同步操作)

      3. 更好的語義

        async 和 await,比起 * 號和 yield ,語義更清楚了, async 表示函數里有異步操作, await 表示緊跟在后面的表達式需要等待結果

    • 缺點

      因為 await 將異步代碼改造成了同步代碼,如果多個異步代碼沒有依賴性卻使用了 await 會導致性能的降低,代碼沒有依賴性的話,完全可以使用 Promise.all 的方式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM