1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        用圖表學習掌握 異步/同步知識

        共 8919字,需瀏覽 18分鐘

         ·

        2022-07-05 06:35

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

        翻譯 | 楊小愛


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

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

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

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

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

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

        異步/同步的基

        一句話總結異步/同步的用法就是:以同步的方式進行異步操作。

        比如有這樣一個場景:我們需要請求一個API,收到響應后,再請求另一個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,收到響應后,再以之前的響應作為參數(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 })})

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

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

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

        示例 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 關鍵字之后的表達式的結果返回,然后再繼續(xù)執(zhí)行下面的代碼。

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

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

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

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

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

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

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

        這是一個例子:

        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()返回的結果的value屬性是未定義的。

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

        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í)行,它應該是這樣的:

        yield a function

        如果yield后面跟著函數(shù)調(diào)用,那么這里程序執(zhí)行完之后,會立即調(diào)用函數(shù)。并且函數(shù)的返回值會放在 g.next() 的結果的 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() 時,返回的結果都與我們之前的情況沒有什么不同。而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 }) })})

        其實上面的寫法和async/await很像。

        唯一的區(qū)別是:

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

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

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

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

        現(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ù)的返回值應該是一個 Promise 對象,所以我們的 generatorToAsync 函數(shù)的模板應該是這樣的:

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

        然后,我們可以將前面的代碼復制到 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,而在實際項目中,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

        結論

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


        學習更多技能

        請點擊下方公眾號

        瀏覽 27
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            在线观看成人毛片 | 亚洲免费人成在线观看网站 | 国产伦精品一二三区视频妓女 | 日本一本一道 | 玖辛奈脱了内裤撅起来屁股被打 | 成年人免费片 | 亚洲系列 | 国产男生操女生 | 国产精品成人无码a 无码 | 99日韩无码 |