<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>

          用圖表學(xué)習(xí)掌握 異步/同步知識

          共 8919字,需瀏覽 18分鐘

           ·

          2022-07-05 06:35

          英文 | https://medium.com/frontend-canteen/you-can-master-async-await-with-7-diagrams-ac96a97abe92

          翻譯 | 楊小愛


          您可能已經(jīng)閱讀了一些關(guān)于 異步/同步 的文章,甚至使用它們編寫了一些代碼。但是你真的掌握了異步/同步嗎?

          在本文中,讓我們討論以下主題:

          • 異步/同步的基本用法。

          • 然后我們了解異步的祖先,生成器函數(shù)。

          • 最后,讓我們自己實(shí)現(xiàn) 異步/同步

          我準(zhǔn)備了 7 個圖表來解釋這些概念,希望它們能幫助您更輕松地理解這些主題。

          異步/同步的基礎(chǔ)

          一句話總結(jié)異步/同步的用法就是:以同步的方式進(jìn)行異步操作。

          比如有這樣一個場景:我們需要請求一個API,收到響應(yīng)后,再請求另一個API。

          然后我們可以這樣寫代碼:

          function request(num) { // mock HTTP request  return new Promise(resolve => {    setTimeout(() => {      resolve(num * 2)    }, 1000)  })}
          request(1).then(res1 => { console.log(res1) // it will print `2` after 1 second
          request(2).then(res2 => { console.log(res2) // it will print `4` after anther 1 second })})

          或者還有另外一種場景:我們需要請求一個API,收到響應(yīng)后,再以之前的響應(yīng)作為參數(shù)請求另一個API。

          然后我們可以這樣寫代碼:

          request(5).then(res1 => {  console.log(res1) // it will print `10` after 1 second
          request(res1).then(res2 => { console.log(res2) // it will print `20` after anther 1 second })})

          上面兩段代碼確實(shí)可以解決問題,但是如果嵌套層級太多,代碼就會不美觀、不可讀。

          解決這個問題的方法是使用異步/同步。它允許我們以同步的方式執(zhí)行異步操作。

          異步/同步重構(gòu)以上兩段代碼后,它們看起來像這樣:

          示例 1:

          async function fn () {  await request(1)  await request(2)}fn()

          示例 2:

          async function fn () {const res1 = await request(5)const res2 = await request(res1)console.log(res2)}fn()

          JavaScript 引擎會等待 await 關(guān)鍵字之后的表達(dá)式的結(jié)果返回,然后再繼續(xù)執(zhí)行下面的代碼。

          以上代碼執(zhí)行流程示意圖:

          就像你在加油站加油一樣,只有當(dāng)前一輛車加滿油后,才能輪到下一輛車加油。在async函數(shù)中,await指定異步操作只能在隊列中一個一個執(zhí)行,從而達(dá)到以同步方式執(zhí)行異步操作的效果。

          注意:await 關(guān)鍵字只能用在 async 函數(shù)中,否則會報錯。

          那我們要知道await后面不能跟普通函數(shù),否則就達(dá)不到排隊的效果。

          下面的代碼是一個不正確的例子。

          function request(num) {  setTimeout(() => {    console.log(num * 2)  }, 1000)}
          async function fn() { await request(1) // 2 await request(2) // 4 // print `2` and `4` at the same time}fn()

          生成器函數(shù)

          async/await 本身的用法很簡單,但它實(shí)際上是一種語法糖。async/await 是 ES2017 中引入的一種語法。如果你嘗試將async/await語法的代碼編譯到ES2015版本,你會發(fā)現(xiàn)它們會被編譯成generate函數(shù),所以這里我們先了解generate函數(shù)。

          生成器函數(shù)是使用 function* 語法編寫的。調(diào)用時,生成器函數(shù)最初不會執(zhí)行它們的代碼。相反,它們返回一種特殊類型的迭代器,稱為生成器。當(dāng)調(diào)用生成器的 next 方法消耗了一個值時,生成器函數(shù)會一直執(zhí)行,直到遇到 yield 關(guān)鍵字。

          這是一個例子:

          function* gen() {  yield 1  yield 2  yield 3}const g = gen()console.log(g.next()) // { value: 1, done: false }console.log(g.next()) // { value: 2, done: false }console.log(g.next()) // { value: 3, done: false }console.log(g.next()) // { value: undefined, done: true }

          上面代碼中,gen函數(shù)沒有返回值,所以最后一次調(diào)用g.next()返回的結(jié)果的value屬性是未定義的。

          如果 generate 函數(shù)有返回值,那么最后一次調(diào)用 g.next() 返回的結(jié)果的 value 屬性就是結(jié)果。

          function* gen() {  yield 1  yield 2  yield 3  return 4}const g = gen()console.log(g.next()) // { value: 1, done: false }console.log(g.next()) // { value: 2, done: false }console.log(g.next()) // { value: 3, done: false }console.log(g.next()) // { value: 4, done: true }

          如果我們用一張圖來表示上述函數(shù)的執(zhí)行,它應(yīng)該是這樣的:

          yield a function

          如果yield后面跟著函數(shù)調(diào)用,那么這里程序執(zhí)行完之后,會立即調(diào)用函數(shù)。并且函數(shù)的返回值會放在 g.next() 的結(jié)果的 value 屬性中。

          function fn(num) {  console.log(num)  return num}function* gen() {  yield fn(1)  yield fn(2)  return 3}const g = gen()console.log(g.next()) // 1// { value: 1, done: false }console.log(g.next())// 2//  { value: 2, done: false }console.log(g.next()) // { value: 3, done: true }

          Promise 

          同樣,Promise 對象也可以放在 yield 之后。那么程序的執(zhí)行流程和之前一樣。

          function fn(num) {  return new Promise(resolve => {    setTimeout(() => {      resolve(num)    }, 1000)  })}function* gen() {  yield fn(1)  yield fn(2)  return 3}const g = gen()console.log(g.next()) // { value: Promise { <pending> }, done: false }console.log(g.next()) // { value: Promise { <pending> }, done: false }console.log(g.next()) // { value: 3, done: true }

          此代碼的執(zhí)行流程示意圖:

          但是,我們要的不是處于pending狀態(tài)的Promise對象,而是Promise完成后存儲在其中的值。那么我們?nèi)绾涡薷纳厦娴拇a呢?

          很簡單,我們只需要調(diào)用 .then 方法:

          const g = gen()const next1 = g.next()next1.value.then(res1 => {  console.log(next1) // print { value: Promise { 1 }, done: false } after 1 second  console.log(res1) // print `1` after 1 second
          const next2 = g.next() next2.value.then(res2 => { console.log(next2) // print { value: Promise { 2 }, done: false } after 2 seconds console.log(res2) // print `2` after 2 seconds console.log(g.next()) // print { value: 3, done: true } after 2 seconds })})

          以上代碼執(zhí)行流程示意圖:

          在 next() 中傳遞一個參數(shù)

          然后,在調(diào)用 next() 函數(shù)時,我們可以傳遞參數(shù)。

          function* gen() {  const num1 = yield 1  console.log(num1)  const num2 = yield 2  console.log(num2)  return 3}const g = gen()console.log(g.next()) // { value: 1, done: false }console.log(g.next(11111))// 11111//  { value: 2, done: false }console.log(g.next(22222)) // 22222// { value: 3, done: true }

          這里需要注意的是,第一次調(diào)用next()方法時,傳參是沒有作用的。

          每次調(diào)用 g.next() 時,返回的結(jié)果都與我們之前的情況沒有什么不同。而num1會接受g.next(11111)的參數(shù)11111,num2會接受g.next(11111)的參數(shù)22222。

          此代碼的執(zhí)行流程示意圖:

          Promise + Pass param

          之前我們提到過Promise對象可以放在yield之后,我們也提到過可以在next函數(shù)中傳入?yún)?shù)。

          如果我們將這兩個功能放在一起,它會變成這樣:

          function fn(nums) {  return new Promise(resolve => {    setTimeout(() => {      resolve(nums * 2)    }, 1000)  })}function* gen() {  const num1 = yield fn(1)  const num2 = yield fn(num1)  const num3 = yield fn(num2)  return num3}const g = gen()const next1 = g.next()next1.value.then(res1 => {  console.log(next1) // print { value: Promise { 2 }, done: false } after 1 second  console.log(res1) // print `2` after 1 senond
          const next2 = g.next(res1) // pass privouse result next2.value.then(res2 => { console.log(next2) // print { value: Promise { 4 }, done: false } after 2 seconds console.log(res2) // print `4` after 2 senond
          const next3 = g.next(res2) // pass privouse result `res2` next3.value.then(res3 => { console.log(next3) // print { value: Promise { 8 }, done: false } after 3 seconds console.log(res3) // print `8` after 3 senond
          // pass privouse result `res3` console.log(g.next(res3)) // print { value: 8, done: true } after 3 seconds }) })})

          其實(shí)上面的寫法和async/await很像。

          唯一的區(qū)別是:

          • gen函數(shù)執(zhí)行后,返回值不是Promise對象。但是 asyncFn 的返回值是 Promise

          • gen函數(shù)需要執(zhí)行特定的操作才相當(dāng)于asyncFn的排隊效果

          • gen函數(shù)執(zhí)行的操作是不完善的,它規(guī)定只能處理三層嵌套

          下面我們將解決這些問題并自己實(shí)現(xiàn) async/await。

          實(shí)現(xiàn)async/await

          為了解決前面提到的問題,我們可以封裝一個高階函數(shù)。這個高階函數(shù)可以接受一個生成器函數(shù),經(jīng)過一系列的處理,返回一個新的函數(shù),工作起來就像一個真正的異步函數(shù)。

          function generatorToAsync(generatorFn) {  // do something  return `a function works like a real async function`}

          異步函數(shù)的返回值應(yīng)該是一個 Promise 對象,所以我們的 generatorToAsync 函數(shù)的模板應(yīng)該是這樣的:

          function* gen() {
          }function generatorToAsync (generatorFn) { return function () { return new Promise((resolve, reject) => {
          }) }}
          const asyncFn = generatorToAsync(gen)
          console.log(asyncFn()) // an Promise object

          然后,我們可以將前面的代碼復(fù)制到 generatorToAsync 函數(shù)中:

          function fn(nums) {  return new Promise(resolve => {    setTimeout(() => {      resolve(nums * 2)    }, 1000)  })}function* gen() {  const num1 = yield fn(1)  const num2 = yield fn(num1)  const num3 = yield fn(num2)  return num3}function generatorToAsync(generatorFn) {  return function () {    return new Promise((resolve, reject) => {      const g = generatorFn()      const next1 = g.next()      next1.value.then(res1 => {
          const next2 = g.next(res1) next2.value.then(res2 => {
          const next3 = g.next(res2) next3.value.then(res3 => {
          resolve(g.next(res3).value) }) }) }) }) }}
          const asyncFn = generatorToAsync(gen)
          asyncFn().then(res => console.log(res))

          但是,上面的代碼只能處理三個yield,而在實(shí)際項目中,yield的個數(shù)是不確定的,可能是3、5或10。所以我們還需要調(diào)整代碼,讓我們的generatorToAsync函數(shù)可以處理任何 產(chǎn)量數(shù):

          function generatorToAsync(generatorFn) {  return function() {    const gen = generatorFn.apply(this, arguments) // there may be arguments of gen function
          // return a Promise object return new Promise((resolve, reject) => {
          function go(key, arg) { let res try { res = gen[key](arg) } catch (error) { return reject(error) }
          // get `value` and `done` const { value, done } = res if (done) { // if `done` is true, meaning there isn't any yield left. Then we can resolve(value) return resolve(value) } else { // if `done` is false, meaning there are still some yield left.
          // `value` may be a normal value or a Promise object return Promise.resolve(value).then(val => go('next', val), err => go('throw', err)) } }
          go("next") }) }}
          const asyncFn = generatorToAsync(gen)
          asyncFn().then(res => console.log(res))

          用法

          異步/等待版本代碼:

          async function asyncFn() {  const num1 = await fn(1)  console.log(num1) // 2  const num2 = await fn(num1)  console.log(num2) // 4  const num3 = await fn(num2)  console.log(num3) // 8  return num3}const asyncRes = asyncFn()console.log(asyncRes) // an Promise objectasyncRes.then(res => console.log(res)) // 8

          generatorToAsync 版本代碼:

          function* gen() {  const num1 = yield fn(1)  console.log(num1) // 2  const num2 = yield fn(num1)  console.log(num2) // 4  const num3 = yield fn(num2)  console.log(num3) // 8  return num3}
          const genToAsync = generatorToAsync(gen)const asyncRes = genToAsync()console.log(asyncRes) // an Promise objectasyncRes.then(res => console.log(res)) // 8

          結(jié)論

          在本文中,我們首先了解了 async/await 的基本用法,然后詳細(xì)介紹了生成器函數(shù)的用法。async/await 本質(zhì)上是生成器函數(shù)的語法糖。最后,我們使用生成器函數(shù)來實(shí)現(xiàn) async/await。
          希望今天的內(nèi)容對你有用,謝謝你的閱讀。
          最后,如果你覺得有幫助的話,請點(diǎn)贊我,關(guān)注我,并將它分享給你身邊做開發(fā)的朋友,也許能夠幫助到他,與你一起學(xué)習(xí)進(jìn)步。


          學(xué)習(xí)更多技能

          請點(diǎn)擊下方公眾號

          瀏覽 27
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  日逼小视频 | www.一区二区三区 | 五月天婷婷色网 | 亚洲天堂在线免费观看 | 亚洲中文幕在线观看 |