溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

javascript Async函數(shù)相關(guān)知識(shí)點(diǎn)有哪些

發(fā)布時(shí)間:2021-11-11 11:31:12 來(lái)源:億速云 閱讀:129 作者:iii 欄目:web開發(fā)

本篇內(nèi)容介紹了“javascript Async函數(shù)相關(guān)知識(shí)點(diǎn)有哪些”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

前言

在異步處理方案中,目前最為簡(jiǎn)潔優(yōu)雅的便是async函數(shù)(以下簡(jiǎn)稱A函數(shù))。經(jīng)過(guò)必要的分塊包裝后,A函數(shù)能使多個(gè)相關(guān)的異步操作如同同步操作一樣聚合起來(lái),使其相互間的關(guān)系更為清晰、過(guò)程更為簡(jiǎn)潔、調(diào)試更為方便。它本質(zhì)是Generator函數(shù)的語(yǔ)法糖,通俗的說(shuō)法是使用G函數(shù)進(jìn)行異步處理的增強(qiáng)版。

嘗試

學(xué)習(xí)A函數(shù)必須有Promise基礎(chǔ),***還了解Generator函數(shù),有需要的可查看延伸小節(jié)。

為了直觀的感受A函數(shù)的魅力,下面使用Promise和A函數(shù)進(jìn)行了相同的異步操作。該異步的目的是獲取用戶的留言列表,需要分頁(yè),分頁(yè)由后臺(tái)控制。具體的操作是:先獲取到留言的總條數(shù),再更正當(dāng)前需要顯示的頁(yè)數(shù)(每次切換到不同頁(yè)時(shí),總數(shù)目可能會(huì)發(fā)生變化),***傳遞參數(shù)并獲取到相應(yīng)的數(shù)據(jù)。

let totalNum = 0; // Total comments number.  let curPage = 1; // Current page index.  let pageSize = 10; // The number of comment displayed in one page.  // 使用A函數(shù)的主代碼。  async function dealWithAsync() {  totalNum = await getListCount();  console.log('Get count', totalNum); if (pageSize * (curPage - 1) > totalNum) {  curPage = 1;  }  return getListData();  }  // 使用Promise的主代碼。  function dealWithPromise() {  return new Promise((resolve, reject) => {  getListCount().then(res => {  totalNum = res;  console.log('Get count', res);  if (pageSize * (curPage - 1) > totalNum) {  curPage = 1;  }  return getListData()  }).then(resolve).catch(reject);  });  }  // 開始執(zhí)行dealWithAsync函數(shù)。  // dealWithAsync().then(res => {  // console.log('Get Data', res)  // }).catch(err => {  // console.log(err);  // });  // 開始執(zhí)行dealWithPromise函數(shù)。  // dealWithPromise().then(res => {  // console.log('Get Data', res)  // }).catch(err => {  // console.log(err);  // });  function getListCount() { return createPromise(100).catch(() => {  throw 'Get list count error';  });  }  function getListData() {  return createPromise([], {  curPage: curPage,  pageSize: pageSize,  }).catch(() => {  throw 'Get list data error';  });  }  function createPromise(  data, // Reback data  params = null, // Request params  isSucceed = true,  timeout = 1000,  ) {  return new Promise((resolve, reject) => {  setTimeout(() => {  isSucceed ? resolve(data) : reject(data);  }, timeout);  });  }

對(duì)比dealWithAsync和dealWithPromise兩個(gè)簡(jiǎn)單的函數(shù),能直觀的發(fā)現(xiàn):使用A函數(shù),除了有await關(guān)鍵字外,與同步代碼無(wú)異。而使用Promise則需要根據(jù)規(guī)則增加很多包裹性的鏈?zhǔn)讲僮?,產(chǎn)生了太多回調(diào)函數(shù),不夠簡(jiǎn)約。另外,這里分開了每個(gè)異步操作,并規(guī)定好各自成功或失敗時(shí)傳遞出來(lái)的數(shù)據(jù),近乎實(shí)際開發(fā)。

1 登堂

1.1 形式

A函數(shù)也是函數(shù),所以具有普通函數(shù)該有的性質(zhì)。不過(guò)形式上有兩點(diǎn)不同:一是定義A函數(shù)時(shí),function關(guān)鍵字前需要有async關(guān)鍵字(意為異步),表示這是個(gè)A函數(shù)。二是在A函數(shù)內(nèi)部可以使用await關(guān)鍵字(意為等待),表示會(huì)將其后面跟隨的結(jié)果當(dāng)成異步操作并等待其完成。

以下是它的幾種定義方式。

// 聲明式  async function A() {}  // 表達(dá)式  let A = async function () {};  // 作為對(duì)象屬性  let o = {  A: async function () {}  };  // 作為對(duì)象屬性的簡(jiǎn)寫式  let o = {  async A() {}  };  // 箭頭函數(shù)  let o = {  A: async () => {}  };

1.2 返回值

執(zhí)行A函數(shù),會(huì)固定的返回一個(gè)Promise對(duì)象。

得到該對(duì)象后便可監(jiān)設(shè)置成功或失敗時(shí)的回調(diào)函數(shù)進(jìn)行監(jiān)聽。如果函數(shù)執(zhí)行順利并結(jié)束,返回的P對(duì)象的狀態(tài)會(huì)從等待轉(zhuǎn)變成成功,并輸出return命令的返回結(jié)果(沒有則為undefined)。如果函數(shù)執(zhí)行途中失敗,JS會(huì)認(rèn)為A函數(shù)已經(jīng)完成執(zhí)行,返回的P對(duì)象的狀態(tài)會(huì)從等待轉(zhuǎn)變成失敗,并輸出錯(cuò)誤信息。

// 成功執(zhí)行案例  A1().then(res => {  console.log('執(zhí)行成功', res); // 10  });  async function A1() {  let n = 1 * 10;  return n;  }  // 失敗執(zhí)行案例  A2().catch(err => {  console.log('執(zhí)行失敗', err); // i is not defined.  });  async function A2() {  let n = 1 * i;  return n;  }

1.3 await

只有在A函數(shù)內(nèi)部才可以使用await命令,存在于A函數(shù)內(nèi)部的普通函數(shù)也不行。

引擎會(huì)統(tǒng)一將await后面的跟隨值視為一個(gè)Promise,對(duì)于不是Promise對(duì)象的值會(huì)調(diào)用Promise.resolve()進(jìn)行轉(zhuǎn)化。即便此值為一個(gè)Error實(shí)例,經(jīng)過(guò)轉(zhuǎn)化后,引擎依然視其為一個(gè)成功的Promise,其數(shù)據(jù)為Error的實(shí)例。

當(dāng)函數(shù)執(zhí)行到await命令時(shí),會(huì)暫停執(zhí)行并等待其后的Promise結(jié)束。如果該P(yáng)對(duì)象最終成功,則會(huì)返回成功的返回值,相當(dāng)將await  xxx替換成返回值。如果該P(yáng)對(duì)象最終失敗,且錯(cuò)誤沒有被捕獲,引擎會(huì)直接停止執(zhí)行A函數(shù)并將其返回對(duì)象的狀態(tài)更改為失敗,輸出錯(cuò)誤信息。

***,A函數(shù)中的return x表達(dá)式,相當(dāng)于return await x的簡(jiǎn)寫。

// 成功執(zhí)行案例  A1().then(res => {  console.log('執(zhí)行成功', res); // 約兩秒后輸出100。  });  async function A1() {  let n1 = await 10;  let n2 = await new Promise(resolve => {  setTimeout(() => {  resolve(10);  }, 2000);  });  return n1 * n2;  }  // 失敗執(zhí)行案例  A2().catch(err => {  console.log('執(zhí)行失敗', err); // 約兩秒后輸出10。  });  async function A2() {  let n1 = await 10;  let n2 = await new Promise((resolve, reject) => {  setTimeout(() => {  reject(10);  }, 2000);  });  return n1 * n2; }

2 入室

2.1 繼發(fā)與并發(fā)

對(duì)于存在于JS語(yǔ)句(for, while等)的await命令,引擎遇到時(shí)也會(huì)暫停執(zhí)行。這意味著可以直接使用循環(huán)語(yǔ)句處理多個(gè)異步。

以下是處理繼發(fā)的兩個(gè)例子。A函數(shù)處理相繼發(fā)生的異步尤為簡(jiǎn)潔,整體上與同步代碼無(wú)異。

// 兩個(gè)方法A1和A2的行為結(jié)果相同,都是每隔一秒輸出10,輸出三次。  async function A1() {  let n1 = await createPromise();  console.log('N1', n1);  let n2 = await createPromise();  console.log('N2', n2);  let n3 = await createPromise();  console.log('N3', n3);  }  async function A2() {  for (let i = 0; i< 3; i++) {  let n = await createPromise();  console.log('N' + (i + 1), n);  }  }  function createPromise() {  return new Promise(resolve => {  setTimeout(() => {  resolve(10);  }, 1000);  });  }

接下來(lái)是處理并發(fā)的三個(gè)例子。A1函數(shù)使用了Promise.all生成一個(gè)聚合異步,雖然簡(jiǎn)單但靈活性降低了,只有都成功和失敗兩種情況。A3函數(shù)相對(duì)A2僅僅為了說(shuō)明應(yīng)該怎樣配合數(shù)組的遍歷方法使用async函數(shù)。重點(diǎn)在A2函數(shù)的理解上。

A2函數(shù)使用了循環(huán)語(yǔ)句,實(shí)際是繼發(fā)的獲取到各個(gè)異步值,但在總體的時(shí)間上相當(dāng)并發(fā)(這里需要好好理解一番)。因?yàn)橐婚_始創(chuàng)建reqs數(shù)組時(shí),就已經(jīng)開始執(zhí)行了各個(gè)異步,之后雖然是逐一繼發(fā)獲取,但總花費(fèi)時(shí)間與遍歷順序無(wú)關(guān),恒等于耗時(shí)最多的異步所花費(fèi)的時(shí)間(不考慮遍歷、執(zhí)行等其它的時(shí)間消耗)。

// 三個(gè)方法A1, A2和A3的行為結(jié)果相同,都是在約一秒后輸出[10, 10, 10]。  async function A1() { let res = await Promise.all([createPromise(), createPromise(), createPromise()]);  console.log('Data', res);  }  async function A2() {  let res = [];  let reqs = [createPromise(), createPromise(), createPromise()];  for (let i = 0; i< reqs.length; i++) {  res[i] = await reqs[i];  }  console.log('Data', res);  }  async function A3() {  let res = [];  let reqs = [9, 9, 9].map(async (item) => {  let n = await createPromise(item);  return n + 1;  });  for (let i = 0; i< reqs.length; i++) {  res[i] = await reqs[i];  }  console.log('Data', res);  }  function createPromise(n = 10) {  return new Promise(resolve => {  setTimeout(() => {  resolve(n);  }, 1000);  });  }

2.2 錯(cuò)誤處理

一旦await后面的Promise轉(zhuǎn)變成rejected,整個(gè)async函數(shù)便會(huì)終止。然而很多時(shí)候我們不希望因?yàn)槟硞€(gè)異步操作的失敗,就終止整個(gè)函數(shù),因此需要進(jìn)行合理錯(cuò)誤處理。注意,這里所說(shuō)的錯(cuò)誤不包括引擎解析或執(zhí)行的錯(cuò)誤,僅僅是狀態(tài)變?yōu)閞ejected的Promise對(duì)象。

處理的方式有兩種:一是先行包裝Promise對(duì)象,使其始終返回一個(gè)成功的Promise。二是使用try.catch捕獲錯(cuò)誤。

// A1和A2都執(zhí)行成,且返回值為10。  A1().then(console.log);  A2().then(console.log);  async function A1() {  let n;  n = await createPromise(true);  return n;  }  async function A2() {  let n;  try {  n = await createPromise(false);  } catch (e) {  n = e;  }  return n;  }  function createPromise(needCatch) {  let p = new Promise((resolve, reject) => {  reject(10);  });  return needCatch ? p.catch(err => err) : p;  }

2.3 實(shí)現(xiàn)原理

前言中已經(jīng)提及,A函數(shù)是使用G函數(shù)進(jìn)行異步處理的增強(qiáng)版。既然如此,我們就從其改進(jìn)的方面入手,來(lái)看看其基于G函數(shù)的實(shí)現(xiàn)原理。A函數(shù)相對(duì)G函數(shù)的改進(jìn)體現(xiàn)在這幾個(gè)方面:更好的語(yǔ)義,內(nèi)置執(zhí)行器和返回值是Promise。

更好的語(yǔ)義。G函數(shù)通過(guò)在function后使用*來(lái)標(biāo)識(shí)此為G函數(shù),而A函數(shù)則是在function前加上async關(guān)鍵字。在G函數(shù)中可以使用yield命令暫停執(zhí)行和交出執(zhí)行權(quán),而A函數(shù)是使用await來(lái)等待異步返回結(jié)果。很明顯,async和await更為語(yǔ)義化。

// G函數(shù)  function* request() {  let n = yield createPromise();  }  // A函數(shù)  async function request() {  let n = await createPromise();  }  function createPromise() {  return new Promise(resolve => {  setTimeout(() => {  resolve(10);  }, 1000);  });  }

內(nèi)置執(zhí)行器。調(diào)用A函數(shù)便會(huì)一步步自動(dòng)執(zhí)行和等待異步操作,直到結(jié)束。如果需要使用G函數(shù)來(lái)自動(dòng)執(zhí)行異步操作,需要為其創(chuàng)建一個(gè)自執(zhí)行器。通過(guò)自執(zhí)行器來(lái)自動(dòng)化G函數(shù)的執(zhí)行,其行為與A函數(shù)基本相同。可以說(shuō),A函數(shù)相對(duì)G函數(shù)***改進(jìn)便是內(nèi)置了自執(zhí)行器。

// 兩者都是每隔一秒鐘打印出10,重復(fù)兩次。  // A函數(shù)  A();  async function A() {  let n1 = await createPromise();  console.log(n1);  let n2 = await createPromise();  console.log(n2);  }  // G函數(shù),使用自執(zhí)行器執(zhí)行。  spawn(G);  function* G() {  let n1 = yield createPromise();  console.log(n1); let n2 = yield createPromise();  console.log(n2);  }  function spawn(genF) {  return new Promise(function(resolve, reject) {  const gen = genF();  function step(nextF) {  let next;  try {  next = nextF();  } catch(e) {  return reject(e);  }  if(next.done) {  return resolve(next.value);  }  Promise.resolve(next.value).then(function(v) {  step(function() { return gen.next(v); });  }, function(e) {  step(function() { return gen.throw(e); });  });  }  step(function() { return gen.next(undefined); });  });  }  function createPromise() {  return new Promise(resolve => {  setTimeout(() => { resolve(10);  }, 1000);  });  }

2.4 執(zhí)行順序

在了解A函數(shù)內(nèi)部與包含它外部間的執(zhí)行順序前,需要明白兩點(diǎn):一為Promise的實(shí)例方法是推遲到本輪事件末尾才執(zhí)行的后執(zhí)行操作,詳情請(qǐng)查看鏈接。二為Generator函數(shù)是通過(guò)調(diào)用實(shí)例方法來(lái)切換執(zhí)行權(quán)進(jìn)而控制程序執(zhí)行順序,詳情請(qǐng)查看鏈接。理解好A函數(shù)的執(zhí)行順序,能更加清楚的把握此三者的存在。

先看以下代碼,對(duì)比A1、A2和A3方法的結(jié)果。

F(A1); // 接連打印出:1 3 4 2 5。F(A2); // 接連打印出:1 3 2 4 5。F(A3); // 先打印出:1 3 2,隔兩秒后打印出:4 9。function F(A) {  console.log(1);  A().then(console.log);  console.log(2);  }  async function A1() {  console.log(3);  console.log(4);  return 5;  }  async function A2() {  console.log(3);  let n = await 5;  console.log(4);  return n;  }  async function A3() {  console.log(3);  let n = await createPromise();  console.log(4);  return n;  }  function createPromise() {  return new Promise(resolve => {  setTimeout(() => {  resolve(9);  }, 2000);  });  }

從結(jié)果上可歸納出一些表面形態(tài)。執(zhí)行A函數(shù),會(huì)即刻執(zhí)行其函數(shù)體,直到遇到await命令。遇到await命令后,執(zhí)行權(quán)會(huì)轉(zhuǎn)向A函數(shù)外部,即不管A函數(shù)內(nèi)部執(zhí)行而開始執(zhí)行外部代碼。執(zhí)行完外部代碼(本輪事件)后,才繼續(xù)執(zhí)行之前await命令后面的代碼。

歸納到此已成功一半,之后著手分析其成因。如果客官您對(duì)本樓有所了解,那一定不會(huì)忘記&lsquo;自執(zhí)行器&rsquo;這位大嬸吧?估計(jì)是忘記了。A函數(shù)的本質(zhì)就是帶有自執(zhí)行器的G函數(shù),所以探究A函數(shù)的執(zhí)行原理就是探究使用自執(zhí)行器的G函數(shù)的執(zhí)行原理。想起了?

再看下面代碼,使用相同邏輯的G函數(shù)會(huì)得到與A函數(shù)相同的結(jié)果。

F(A); // 先打印出:1 3 2,隔兩秒后打印出:4 9。  F(() => {  return spawn(G);  }); // 先打印出:1 3 2,隔兩秒后打印出:4 9。  function F(A) {  console.log(1);  A().then(console.log);  console.log(2);  }  async function A() {  console.log(3);  let n = await createPromise();  console.log(4);  return n;  }  function* G() {  console.log(3);  let n = yield createPromise();  console.log(4);  return n;  }  function createPromise() {  return new Promise(resolve => {  setTimeout(() => {  resolve(9);  }, 2000);  });  }  function spawn(genF) {  return new Promise(function(resolve, reject) {  const gen = genF();  function step(nextF) {  let next;  try {  next = nextF();  } catch(e) {  return reject(e);  }  if(next.done) {  return resolve(next.value);  }  Promise.resolve(next.value).then(function(v) {  step(function() { return gen.next(v); });  }, function(e) {  step(function() { return gen.throw(e); });  });  } step(function() { return gen.next(undefined); });  });  }

自動(dòng)執(zhí)行G函數(shù)時(shí),遇到y(tǒng)ield命令后會(huì)使用Promise.resolve包裹其后的表達(dá)式,并為其設(shè)置回調(diào)函數(shù)。無(wú)論該P(yáng)romise是立刻有了結(jié)果還是過(guò)某段時(shí)間之后,其回調(diào)函數(shù)都會(huì)被推遲到在本輪事件末尾執(zhí)行。之后再是下一步,再下一步。同樣的道理適用于A函數(shù),當(dāng)遇到await命令時(shí)(此處略去三五字),所以有了如此這般的執(zhí)行順序。

“javascript Async函數(shù)相關(guān)知識(shí)點(diǎn)有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI