async 和 await

這段時間一直在重構(gòu)項目,遇見很多請求“高并發(fā)”,因為涉及到多個請求又或者多個連續(xù)請求。之所以給高并發(fā)帶上引號,因為面對大量請求的時候,我們需要調(diào)整好姿勢,怎樣去好好的去寫異步回調(diào),弄清各個請求的順序,稍微不注意可能就掉坑了,可能調(diào)試半天看著vue-devtool控制臺打印的自己以為的“完美的數(shù)據(jù)”,但是頁面各種顯示不聽話。哦,那說明你掉坑了!
理清好執(zhí)行的先后順序,其次再寫回調(diào)的時候我們就要選好正確的方法和正確的姿勢,這樣才不會造成將來你寫的代碼你認(rèn)不清的尷尬,同時感覺代碼一目了然!
同時考慮到異步回調(diào)我們需要理解一些知識:
js的運行機制:
在代碼運行時會形成任務(wù)隊列,分為同步任務(wù)隊列和異步任務(wù)對列,同步隊列優(yōu)先加載,異步任務(wù)隊列會形成隊列任務(wù)池,定時器不會一下加載到異步任務(wù),而是在設(shè)定的時間后加載到異步任務(wù),即使設(shè)置為0,瀏覽器也有它的響應(yīng)時間,以前是10ms.現(xiàn)在是4ms.異步任務(wù)包括dom 事件,定時器,promise
首先作為異步回調(diào)的功能和作用不去作過多解釋,對于js這種單線程異步回調(diào)是性能優(yōu)化的一些點。
異步回調(diào)我覺得主要有兩方面作用:

  • 不阻礙程序運行,將一些延時較久的函數(shù)異步執(zhí)行,不妨礙正常同步運行的代碼;
  • 一個函數(shù)必須在某個函數(shù)執(zhí)行完成后才能運行,比如說需要用函數(shù)執(zhí)行完后的某些數(shù)據(jù);
    首先寫異步回調(diào)的姿勢大概有這樣幾種
  1. 直接函數(shù)套函數(shù)(通俗的講)
    就像這樣
function fn(callback) {
  setTimeout(() => {
    callback()
  }, 1000);
}
function f1() {
  console.log('f1')
}
fn(f1)
// fn
// f1

這樣我們感覺還行,還不錯,還能接受,那如果有另外一個f2函數(shù),f1像fn那樣,在多個f3:

function fn(callback) {
  setTimeout(() => {
    callback(f2)
  }, 1000);
  console.log('fn')
}
function f1(callback) {
  setTimeout(() => {
    callback(f3)
  }, 1000);
  console.log('f1')
}
function f2(callback) {
  setTimeout(() => {
    callback(f3)
  }, 1000);
  console.log('f2')
}
function f3() {
  console.log('f3');
}
fn(f1)
// fn
// f1
// f2
//f3

這樣看著代碼也沒這么亂,但是感覺把自己調(diào)懵了,如果想看出它的一些過程,我們將函數(shù)改寫一下

function fn(callback) {
  setTimeout(() => {
    callback((callback) => {
      setTimeout(() => {
        callback()
      }, 1000);
      console.log('f2')
    })
  }, 1000);
  console.log('fn')
}
fn((callback) => {
  setTimeout(() => {
    callback((callback) => {
      console.log('f3')
    })
  }, 1000);
  console.log('f1')
})
// fn
// f1
// f2
// f3

如果連起來看,分清之前的f1,f2,f3函數(shù)很困難吧,這還只是三個回調(diào)函數(shù),有時候不僅僅這些吧,如果再來兩個,咋樣???
所以傳統(tǒng)方法晦澀難懂。。。。

  1. 第二種方法看起來可能就比較爽了------Promise
    Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件,更合理和更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對象。
    所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理
    前兩句話是摘自阮一峰老師的《es6入門》
    我理解的Promise其實就是給你封裝好的一個異步對象,本身有resolve和reject參數(shù),當(dāng)然也是函數(shù),其實這里叫函數(shù)也不太好,我們不如說叫“自定義鉤子”,當(dāng)然這個還不像我們常說的鉤子函數(shù)那樣。Promise對象外部暴露的了兩個分別對應(yīng)的是成功之后的then函數(shù),另一個是reject函數(shù)。說白了這兩個函數(shù)是由用來設(shè)計resolve和reject函數(shù)的。Promise只負(fù)責(zé)將你寫的函數(shù)在內(nèi)部調(diào)用,同時將他的返回值有兩個(這里說的兩個是兩種結(jié)果)傳遞出來,成功之后自然就是resolve(data),失敗是reject(data),當(dāng)然我們寫resolve的代碼塊其實就是Promise實例執(zhí)行完后對應(yīng)的then函數(shù)的執(zhí)行,當(dāng)然data就是then函數(shù)回調(diào)的參數(shù),當(dāng)然reject和catch()也是一樣的道理。其實看著這么像函數(shù)傳參的過程,也這么像“依賴注入”的趕腳。
    不說別的了,直接將上面函數(shù)改寫一下:
function fn() {
  console.log('fn');
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, 1000);
  })
}
function f1() {
  console.log('f1')
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, 1000);
  })
}
function f2() {
  console.log('f2')
  return new Promise(resolve2 => {
    setTimeout(() => {
      resolve2()
    }, 1000);
  })
}
function f3() {
  console.log('f3');
}
fn().then(() => {
  f1().then(() => {
    f2().then(() => {
      f3()
    })
  })
})
// fn
// f1
// f2
// f3

等到講aync和await時再好好講講Promise,今天的項目有一個特點就被埋坑了
有了Promise對象,就可以將異步操作以同步操作的流程表達(dá)出來,避免了層層嵌套的回調(diào)函數(shù)。此外,Promise對象提供統(tǒng)一的接口,使得控制異步操作更加容易,看著上面的代碼是不是清爽多了,但是感覺函數(shù)一多結(jié)構(gòu)看著似乎也不那么友好,同時Promise也有弊端就是獲取錯誤信息的時候,這些就不贅述了,網(wǎng)上應(yīng)該有很多介紹的,今天的主角是async 和 await

  1. async 和 await
    這兩個方法其實就是Generator 函數(shù)的語法糖。詳情請見阮一峰的es6
    先說說背景,今天做一個黑名單的需求:
    拉取軟件列表的時候,首先我需要對list中存在的黑名單來個請求黑名單的請求,同時請求完成后我們需要將黑名單list存儲在vue的data對象里,然后再通過對list進行過濾,對是黑名單的item進行軟件圖標(biāo)的置灰操作,同時在不同的分類我們還需要進行判斷是否進行過黑名單的操作,若果進行過就可以對本分組進行鎖定和解鎖操作,同時后續(xù)操作我們還需要拉取一個已經(jīng)鎖定分組的集合,同時是判定當(dāng)前分組是否屬于已經(jīng)鎖定分組,相應(yīng)的在頁面中對當(dāng)前分組是顯示解鎖按鈕還是鎖定按鈕
    鑒于涉及到這么多的請求,一個請求完成后需要完成幾個請求才能進行相應(yīng)參數(shù),同時需要請求的先后順序很明確。
    剛開始考慮就用Promise,因為封裝的ajax方法就是基于axios的,但涉及到這么多連續(xù)請求先別說結(jié)構(gòu)不友好,可能出錯你都不知道怎么出的,所以我打算用async和await:
    當(dāng)然咱們還是先把上面的那個例子改一下再說項目中的問題:
function f1() {
  console.log('f1')
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, 1000);
  })
}
function f2() {
  console.log('f2')
  return new Promise(resolve2 => {
    setTimeout(() => {
      resolve2()
    }, 1000);
  })
}
function f3() {
  console.log('f3');
}
async function fn() {
  console.log('fn');
  await f1()
  await f2()
  f3()
}
fn()
// fn
// f1
// f2
// f3

這樣寫代碼是不是看起來清爽多了!
今天寫邏輯的時候犯了一個影響智商的錯看代碼和執(zhí)行效果
在公司寫的代碼不變貼上來,所以就來模擬一下函數(shù)的執(zhí)行

  async function fn() {
    await f1()
    console.log('fn')
  }
  function f1() {
    new Promise(resolve => {
      setTimeout(() => {
        console.log('f1')
        resolve()
      }, 1000);
    })
  }
  fn()
// fn
// f1
async function fn() {
    await f1()
    console.log('fn')
  }
  function f1() {
   return new Promise(resolve => {
      setTimeout(() => {
        console.log('f1')
        resolve()
      }, 1000);
    })
  }
  fn()
// f1
// fn

看看兩次執(zhí)行的結(jié)果
第一個函數(shù)塊寫的時候完全不按異步執(zhí)行去操作,總是像一般函數(shù)那樣,await當(dāng)成了異步隊列,我只想說明注意的一個點事用Promise時的return。
我們應(yīng)該了解下面這些事:
Promise有一個性質(zhì)就是立即執(zhí)行,我們需要將它外包一層函數(shù)(就叫fn),當(dāng)我們await fn(),請記住fn只是為了不讓promise立即執(zhí)行,所以我們一定得在fn函數(shù)中返回promise,如果不返回,應(yīng)該知道沒有返回值的函數(shù)的執(zhí)行結(jié)果是undefined,還執(zhí)行個毛線呀!
記住函數(shù)沒有返回值執(zhí)行結(jié)果就是undefined?。。?!
同時還有一個比較常見的問題:

async getList() {
        fetchList(this.listQuery).then(
          ({data}) => {
          let list = data.list
          this.softTotalNum = data.total
          // 獲取黑名單list
          await this.getBlackList()
          list.map(item => {
            if (this.soft_ids.indexOf(item.soft_id) >= 0) {
              item['is_hidden'] = 1
            }
            return item
          })
          this.list = list;
        })
      },
這時候報的錯

其實應(yīng)該這樣寫

getList() {
        fetchList(this.listQuery).then(
         async ({data}) => {
          let list = data.list
          this.softTotalNum = data.total
          // 獲取黑名單list
          await this.getBlackList()
          list.map(item => {
            if (this.soft_ids.indexOf(item.soft_id) >= 0) {
              item['is_hidden'] = 1
            }
            return item
          })
          this.list = list;
        })
      },

一定要在你用的await的最近的父級用async聲明異步函數(shù),其他的頂級父級沒有必要寫async

今天遇到坑后總結(jié)了下面三點:

  1. async是一個異步函數(shù)聲明詞,await必須在async函數(shù)中使用,await后面應(yīng)該你用一個延時函數(shù),當(dāng)然你用一個一般函數(shù)也行,就是立即執(zhí)行而已。根據(jù)單詞的字面意思,await我們可以理解為必須等我后面的函數(shù)執(zhí)行完之后,下面的代碼才能運行。
  2. await的最近父級必須是async函數(shù),否則會報“await is a reserved word”錯。
  3. await對應(yīng)的如果類似于promise的函數(shù),鑒于promise的立即執(zhí)行的特點,我們需要將它外包一層函數(shù)(就叫fn),當(dāng)我們await fn(),請記住fn只是問了不讓promise立即執(zhí)行,所以我們一定得在fn函數(shù)中返回promise,如果不返回,應(yīng)該知道沒有返回值的函數(shù)的執(zhí)行結(jié)果是undefined,還執(zhí)行個毛線呀!
    以上就是一些按async和await的一些知識,當(dāng)然這是es7的東西,記得babel編譯?。?!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容