国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Async:簡潔優(yōu)雅的異步之道

leiyi / 2426人閱讀

摘要:前言在異步處理方案中,目前最為簡潔優(yōu)雅的便是函數(shù)以下簡稱函數(shù)。聲明式表達(dá)式作為對象屬性作為對象屬性的簡寫式箭頭函數(shù)返回值執(zhí)行函數(shù),會固定的返回一個對象。如果該對象最終成功,則會返回成功的返回值,相當(dāng)將替換成返回值。

前言

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

嘗試

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

為了直觀的感受A函數(shù)的魅力,下面使用Promise和A函數(shù)進行了相同的異步操作。該異步的目的是獲取用戶的留言列表,需要分頁,分頁由后臺控制。具體的操作是:先獲取到留言的總條數(shù),再更正當(dāng)前需要顯示的頁數(shù)(每次切換到不同頁時,總數(shù)目可能會發(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);
  });
}

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

1 登堂 1.1 形式

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

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

// 聲明式
async function A() {}

// 表達(dá)式
let A = async function () {};

// 作為對象屬性
let o = {
  A: async function () {}
};

// 作為對象屬性的簡寫式
let o = {
  async A() {}
};

// 箭頭函數(shù)
let o = {
  A: async () => {}
};
1.2 返回值

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

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

// 成功執(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ù)也不行。

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

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

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

// 成功執(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ā)

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

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

// 兩個方法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);
  });
}

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

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

// 三個方法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 錯誤處理

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

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

// 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 實現(xiàn)原理

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

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

// 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ù)便會一步步自動執(zhí)行和等待異步操作,直到結(jié)束。如果需要使用G函數(shù)來自動執(zhí)行異步操作,需要為其創(chuàng)建一個自執(zhí)行器。通過自執(zhí)行器來自動化G函數(shù)的執(zhí)行,其行為與A函數(shù)基本相同。可以說,A函數(shù)相對G函數(shù)最大改進便是內(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í)行順序前,需要明白兩點:一為Promise的實例方法是推遲到本輪事件末尾才執(zhí)行的后執(zhí)行操作,詳情請查看鏈接。二為Generator函數(shù)是通過調(diào)用實例方法來切換執(zhí)行權(quán)進而控制程序執(zhí)行順序,詳情請查看鏈接。理解好A函數(shù)的執(zhí)行順序,能更加清楚的把握此三者的存在。

先看以下代碼,對比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ù),會即刻執(zhí)行其函數(shù)體,直到遇到await命令。遇到await命令后,執(zhí)行權(quán)會轉(zhuǎn)向A函數(shù)外部,即不管A函數(shù)內(nèi)部執(zhí)行而開始執(zhí)行外部代碼。執(zhí)行完外部代碼(本輪事件)后,才繼續(xù)執(zhí)行之前await命令后面的代碼。

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

再看下面代碼,使用相同邏輯的G函數(shù)會得到與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); });
  });
}

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

延伸

ES6精華:Promise
Generator:JS執(zhí)行權(quán)的真實操作者

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97201.html

相關(guān)文章

  • 代碼整潔之道

    摘要:代碼寫得是否整潔是客觀的,是的人或后期維護的人覺得好才是真的好。三代碼設(shè)計原則要想寫出優(yōu)雅整潔的代碼,就要遵循特定的設(shè)計原則。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 最近在做一些項目重構(gòu)的工作,看了不少臟亂差的代碼,身心疲憊。本文將討論如何編寫整潔的代碼,不求高效運行,只求...

    stefan 評論0 收藏0
  • JavaScript:體驗異步優(yōu)雅解決方案

    摘要:但是的的出現(xiàn)碉堡的新朋友,我們可以輕松寫出同步風(fēng)格的代碼同時又擁有異步機制,可以說是目前最簡單,最優(yōu)雅,最佳的解決方案了。不敢說這一定是終極的解決方案,但確實是目前最優(yōu)雅的解決方案 一、異步解決方案的進化史 JavaScript的異步操作一直是個麻煩事,所以不斷有人提出它的各種解決方案。可以追溯到最早的回調(diào)函數(shù)(ajax老朋友),到Promise(不算新的朋友),再到ES6的Gener...

    happyfish 評論0 收藏0
  • [翻譯] Async/Await 使你代碼更簡潔

    摘要:取而代之,利用事件循環(huán)體系,使用了一種類似語法的工作方式一旦非阻塞的異步操作完成之后,就可以讓開發(fā)者分配的回調(diào)函數(shù)被觸發(fā)。第一個嘗試嵌套的回調(diào)函數(shù)下面是使用嵌套的回調(diào)函數(shù)的實現(xiàn)方法這可能對于任何使用者來說再熟悉不過了。 寫在文章前 這篇文章翻譯自 ASYNC/AWAIT WILL MAKE YOUR CODE SIMPLER,這是一篇寫于2017年八月的文章,并由某專欄提名為17年十大...

    hightopo 評論0 收藏0
  • 如何優(yōu)雅實現(xiàn)多個接口并發(fā)?且監(jiān)聽最終結(jié)果

    摘要:相信大家工作中調(diào)用接口的情況很常見,有時候會有這樣的需求進入頁面需要多個接口調(diào)用結(jié)束后,才能讓用戶進行操作而這幾個接口本身并沒有先后順序的要求。最終判斷所有變量值都為。 相信大家工作中調(diào)用接口的情況很常見,有時候會有這樣的需求:進入頁面需要多個接口調(diào)用結(jié)束后,才能讓用戶進行操作!而這幾個接口本身并沒有先后順序的要求。你會怎么做? 1、儲存變量方法 因為接口調(diào)用是異步行為,所以我們可以在...

    shiweifu 評論0 收藏0
  • python基礎(chǔ)教程:異步IO 之 概念和歷史

    摘要:并發(fā)的方式有多種,多線程,多進程,異步等。多線程和多進程之間的場景切換和通訊代價很高,不適合密集型的場景關(guān)于多線程和多進程的特點已經(jīng)超出本文討論的范疇,有興趣的同學(xué)可以自行搜索深入理解。 編程中,我們經(jīng)常會遇到并發(fā)這個概念,目的是讓軟件能充分利用硬件資源,提高性能。并發(fā)的方式有多種,多線程,多進程,異步IO等。多線程和多進程更多應(yīng)用于CPU密集型的場景,比如科學(xué)計算的時間都耗費在CPU...

    BicycleWarrior 評論0 收藏0

發(fā)表評論

0條評論

leiyi

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<