<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【實踐】編寫高質(zhì)量可維護的代碼:異步優(yōu)化

          共 8491字,需瀏覽 17分鐘

           ·

          2021-03-14 02:30

          本文首發(fā)于政采云前端團隊博客:編寫高質(zhì)量可維護的代碼——異步優(yōu)化

          https://www.zoo.team/article/asynchronization-optimizing


          前言

          在現(xiàn)在前端開發(fā)中,異步操作的頻次已經(jīng)越來越高了,特別對于數(shù)據(jù)接口請求和定時器的使用,使得我們不得不關(guān)注異步在業(yè)務(wù)中碰到的場景,以及對異步的優(yōu)化。錯誤的異步處理可能會帶來很多問題,諸如頁面渲染、重復(fù)加載等問題。

          下面我們就先簡單的從 JavaScript 中有大致的哪幾種異步類型為切入點,然后再列舉一些業(yè)務(wù)中我們會碰到的場景來逐個分析下,我們該如何解決。

          異步實現(xiàn)種類

          首先關(guān)于異步實現(xiàn)的方式上大致有如下幾種:

          callback

          callback 即回調(diào)函數(shù)。這家伙出現(xiàn)很早很早了,他其實是處理異步的基本方法。并且回調(diào)的概念不單單出現(xiàn)在 JavaScript,你也會在 Java 或者 C# 等后端語言中也能找到他的影子。

          回調(diào)函數(shù)簡單的說其實就是給另外一個寄主函數(shù)作為傳參的函數(shù)。在寄主函數(shù)執(zhí)行完成或者執(zhí)行到特定階段之后觸發(fā)調(diào)用回調(diào)函數(shù)并執(zhí)行,然后把執(zhí)行結(jié)果再返回給寄主函數(shù)的過程。

          比如我們熟悉的 setTimeout 或者 React 中的 setState 的第二個方法都是以回調(diào)函數(shù)方式去解決異步的實現(xiàn)。

          setTimeout(() => {
             //等待0.2s之后再做具體的業(yè)務(wù)操作
             this.doSomething();
          }, 200);
          this.setState({
            count: res.count,
          }, () => {
            //在更新完count之后再做具體的業(yè)務(wù)操作
            this.doSomething();
          });

          Promise

          Promise 是個好東西,有了它之后我們可以對異步進行很多操作,并且可以把異步以鏈式的方式進行操作。

          其實在 JQuery 中的 deferred 和它就有點像,都是采用回調(diào)函數(shù)的解決方案,都可以做鏈式調(diào)用,但是在 Promise 中增加了錯誤的 catch 方法可以更加方便的處理異常場景,并且它內(nèi)置狀態(tài)(resolve, reject,pending),狀態(tài)只能由 pending 變?yōu)榱硗鈨煞N的其中一種,且改變后不可逆也不可再度修改。

          let promise = new Promise((resolve, reject) => { 
            reject("對不起,你不是我的菜");
          });
          promise.then((data) => {
          console.log('第一次success' + data);
            return '第一次success' + data
          },(error) => {
          console.log(error) }
          ).then((data2) => {
            console.log('第二次success' + data2);
          },(error2) => { 
            console.log(error2) }
          ).catch((e) => {
            console.log('抓到錯誤啦' + e);
          });

          await/async

          await/async 其實是 Promise 的一種升級版本,使用 await/async 調(diào)用異步的時候是從上到下,順序執(zhí)行,就像在寫同步代碼一樣,這更加的符合我們編寫代碼的習慣和思維邏輯,所以容易理解。整體代碼邏輯也會更加的清晰。

          async function asyncDemoFn({
            const data1 = await getData1();
            const data2 = await getData2(data1);
            const data3 =  await getData3(data2);
            console.log(data3)
          }
          await asyncDemoFn()

          generator

          generator 中文名叫構(gòu)造器,是 ES6 中的一個新東西,我相信很多人在現(xiàn)實的代碼中很少能接觸到它,所以它相對而言對大家來說還是比較晦澀,但是這家伙還是很強的,簡單來說它能控制異步調(diào)用,并且其實是一個狀態(tài)機。

          functionfoo({
            for (let i = 1; i <= 3; i++) {
              let x = yield `等我一下唄,i = ${i}`;
              console.log(x);
            }
          }
          setTimeout(() => {
            console.log('終于輪到我了');
          }, 1);
          var a = foo();
          console.log(a); // foo {<closed>}
          var b = a.next();
          console.log(b); // {value: "等我一下唄,i = 1", done: false}
          var c = a.next();
          console.log(c); // {value: "等我一下唄,i = 2", done: false}
          var d = a.next();
          console.log(d); // {value: "等我一下唄,i = 3", done: false}
          var e = a.next();
          console.log(e); // {value: undefined, done: true}
          // 終于輪到我了

          上面代碼的函數(shù) foo 是一個協(xié)程,它的厲害的地方就是 yield 命令。它表示執(zhí)行到此處,執(zhí)行權(quán)將交給其他協(xié)程。也就是說,yield 命令是異步兩個階段的分界線。

          協(xié)程遇到 yield 命令就暫停,等到執(zhí)行權(quán)返回,再從暫停的地方繼續(xù)往后執(zhí)行。它的最大優(yōu)點,就是代碼的寫法非常像同步操作,如果去除 yield 命令,簡直一模一樣。

          再來個有點貼近點場景方式來使用下 generator。比如現(xiàn)在在頁面中我們需要自動的執(zhí)行 checkAuth 和 checkAddress 檢查,我們就用 generator 的方式去實現(xiàn)自動檢查上述兩異步檢查。

          const checkAuth = () => {
              return new Promise((resolve)=>{
                  setTimeout(()=>{
                     resolve('checkAuth1'
                  },1000)
              })
          }
          const checkAddress = () => {
              return new Promise((resolve)=>{
                  setTimeout(()=>{
                      resolve('checkAddress2')
                  },2000)
              })
          }
          var steps = [checkAuth,checkAddress]
          functionfoo(checkList{
            for (let i = 0; i < checkList.length; i++) {
              let x = yield checkList[i]();
              console.log(x);
            }
          }
          var stepsGen = foo(steps)
          var run = async (gen)=>{
              var isFinnish = false
              do{
                 const {done,value} = gen.next()
                 console.log('done:',done)
                 console.log('value:',value)
                 const result = await value
                 console.log('result:',result)
                 
                 isFinnish = done
              }while(!isFinnish)
              console.log('isFinnish:',isFinnish)
          }
          run(stepsGen)

          種類對比

          • 從時間維度從早到晚:callback,Promise,generator,await/async
          • await/async 是目前對于異步的終極形式
          • callback 讓我們有了基本的方式去處理異步情況,Promise 告別了 callback 的回調(diào)地獄并且增加 resolve,reject 和 catch 等方法讓我們能處理不同的情況,generator 增加了對于異步的可操作性,類似一個狀態(tài)機可暫時停住多個異步的執(zhí)行,然后在合適的時候繼續(xù)執(zhí)行剩余的異步調(diào)用,await/async 讓異步調(diào)用更加語義化,并且自動執(zhí)行異步

          異步業(yè)務(wù)中碰到的場景

          回調(diào)地獄

          在使用回調(diào)函數(shù)的時候我們可能會有這樣的場景,B 需要在 A 的返回之后再繼續(xù)調(diào)用,所以在這樣有先后關(guān)系的時候就存在了一個叫回調(diào)地獄的問題了。

          getData1().then((resData1) => {
            getData2(resData1).then((resData2) => {
              getData3(resData2).then((resData3)=>{
                console.log('resData3:', resData3)
              })
            });
          });

          碰到這樣的情況我們可以試著用 await/async 方式去解這種有多個深層嵌套的問題。

          async function asyncDemoFn2({
            const resData1 = await getData1();
            const resData2 = await getData2(resData1);
            const resData3 =  await getData3(resData2);
            console.log(resData3)
          }
          await asyncDemoFn2()

          異步循環(huán)

          在業(yè)務(wù)中我們最最經(jīng)常碰到的就是其實還是存在多個異步調(diào)用的順序問題,大致上可以分為如下幾種:

          并行執(zhí)行

          在并行執(zhí)行的時候,我們可以直接使用 Promise 的 all 方法

          Promise.all([getData1(),getData2(),getData3()]).then(res={
          console.log('res:',res)
          })

          順序執(zhí)行

          在順序執(zhí)行中,我們可以有如下的兩種方式去做

          1. 使用 async/await 配合 for
          const sources = [getData1,getData2,getData3]
          async function promiseQueue({
            console.log('開始');
            for (let targetSource in sources) {
              await targetSource();
            }
            console.log('完成');
          };
          promiseQueue()
          1. 使用 async/await 配合 while
          // getData1,getData2,getData3 都為 promise 對象
          const sources = [getData1,getData2,getData3]
          async function promiseQueue({
            let index = 0
            console.log('開始');
            while(index >=0 && index < sources.length){
              await targetSource();
              index++
            }
            console.log('完成');
          };
          promiseQueue()
          1. 使用 async/await 配合 reduce
          // getData1,getData2,getData3 都為 promise 對象
          const sources = [getData1,getData2,getData3]
          sources.reduce(async (previousValue, currentValue)=>{
            await previousValue
            return currentValue()
          },Promise.resolve())
          1. 使用遞歸
          const sources = [getData1,getData2,getData3]
          function promiseQueue(list , index = 0{
            const len = list.length
            console.log('開始');
            if(index >= 0 && index < len){
              list[index]().then(()=>{
                promiseQueue(list, index+1)      
              })
            }
            console.log('完成');
          }
          promiseQueue(sources)

          結(jié)尾

          今天只是關(guān)于異步的普通使用場景的討論,并且做了些簡單的例子。其實關(guān)于異步的使用還有很多很多復(fù)雜的使用場景。更多的奇思妙想正等著你。

          參考文獻

          JS 異步編程六種方案:(https://juejin.im/post/6844903760280420366#heading-12)

          Async/Await 替代 Promise 的 6 個理由:(https://blog.fundebug.com/2017/04/04/nodejs-async-await/)

          Javascript 異步編程的 4 種方法:(http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html)

          歡迎關(guān)注「前端雜貨鋪」,一個有溫度且致力于前端分享的雜貨鋪

          關(guān)注回復(fù)「加群」,可加入雜貨鋪一起交流學習成長

          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼AV最新地址大全 | 欧美日韩中文字幕在线视频 | 国产黄色网页 | 夜色福利在线看 | 国产日皮视频免费观看 |