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

          面試官問 async、await 函數(shù)原理是在問什么?

          共 10361字,需瀏覽 21分鐘

           ·

          2021-09-12 00:59

          1. 前言

          這周看的是 co 的源碼,我對 co 比較陌生,沒有了解和使用過。因此在看源碼之前,我希望能大概了解 co 是什么,解決了什么問題。

          2. 簡單了解 co

          先看了 co 的 GitHub,README 是這樣介紹的:

          Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.

          看起來有點懵逼,又查了一些資料,大多說 co 是用于 generator 函數(shù)的自動執(zhí)行。generator 是 ES6 提供的一種異步編程解決方案,它最大的特點是可以控制函數(shù)的執(zhí)行。

          2.1 關(guān)于 generator

          說到異步編程,我們很容易想到還有 promise,async 和 await。它們有什么區(qū)別呢?先看看 JS 異步編程進化史:callback -> promise -> generator -> async + await

          JS 異步編程

          再看看它們語法上的差異:

          CallbackPromiseGeneratorasync + await + Promise
          ajax(url, () => {})Promise((resolve,reject) => { resolve() }).then()function* gen() { yield 1}async getData() {  await fetchData() }

          關(guān)于 generator 的學(xué)習(xí)不在此篇幅詳寫了,需要了解它的概念和語法。

          3. 學(xué)習(xí)目標(biāo)

          經(jīng)過簡單學(xué)習(xí),大概明白了 co 產(chǎn)生的背景,因為 generator 函數(shù)不會自動執(zhí)行,需要手動調(diào)用它的 next() 函數(shù),co 的作用就是自動執(zhí)行 generator 的 next() 函數(shù),直到 done 的狀態(tài)變成 true 為止。

          那么我這一期的學(xué)習(xí)目標(biāo):

          1)解讀 co 源碼,理解它是如何實現(xiàn)自動執(zhí)行 generator

          2)動手實現(xiàn)一個簡略版的 co

          4. 解讀 co 源碼

          co 源碼地址:https://github.com/tj/co

          4.1 整體架構(gòu)

          從 README 中,可以看到是如何使用 co :

          co(function* ({
            var result = yield Promise.resolve(true);
            return result;
          }).then(function (value{
            console.log(value);
          }, function (err{
            console.error(err.stack);
          });

          從代碼可以看到它接收了一個 generator 函數(shù),返回了一個 Promise,這部分對應(yīng)的源碼如下。

          function co(gen{
            var ctx = this;
            // 獲取參數(shù)
            var args = slice.call(arguments1);
            // 返回一個 Promise
            return new Promise(function(resolve, reject{
              // 把 ctx 和參數(shù)傳遞給 gen 函數(shù)
              if (typeof gen === 'function') gen = gen.apply(ctx, args);
              // 判斷 gen.next 是否函數(shù),如果不是直接 resolve(gen)
              if (!gen || typeof gen.next !== 'function'return resolve(gen);
              // 先執(zhí)行一次 next
              onFulfilled();
              // 實際上就是執(zhí)行 gen.next 函數(shù),獲取 gen 的值
              function onFulfilled(res{
                var ret;
                try {
                  ret = gen.next(res);
                } catch (e) {
                  return reject(e);
                }
                next(ret);
                return null;
              }
              // 對 gen.throw 的處理
              function onRejected(err{
                var ret;
                try {
                  ret = gen.throw(err);
                } catch (e) {
                  return reject(e);
                }
                next(ret);
              }
              // 實際處理的函數(shù),會遞歸執(zhí)行,直到 ret.done 狀態(tài)為 true
              function next(ret{
                // 如果生成器的狀態(tài) done 為 true,就 resolve(ret.value),返回結(jié)果
                if (ret.done) return resolve(ret.value);
                // 否則,將 gen 的結(jié)果 value 封裝成 Promise
                var value = toPromise.call(ctx, ret.value);
                // 判斷 value 是否 Promise,如果是就返回 then
                if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
                // 如果不是 Promise,Rejected
                return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
                  + 'but the following object was passed: "' + String(ret.value) + '"'));
              }
            });
          }

          看到這里,我產(chǎn)生了一個疑問:Promise + then 也可以處理異步編程,為什么 co 的源碼里要把 Promise + generator 結(jié)合起來呢,為什么要這樣做?直到我搞懂了 co 的核心目的,它使 generator 和 yield 的語法更趨向于同步編程的寫法,引用阮一峰的網(wǎng)絡(luò)日志中的一句話就是:

          異步編程的語法目標(biāo),就是怎樣讓它更像同步編程。

          可以看一個 Promise + then 的例子:

          function getData({
            return new Promise(function(resolve, reject{
              resolve(1111)
            })
          }
          getData().then(function(res{
            // 處理第一個異步的結(jié)果
            console.log(res);
            // 返回第二個異步
            return Promise.resolve(2222)
          })
          .then(function(res{
            // 處理第二個異步的結(jié)果
            console.log(res)
          })
          .catch(function(err{
            console.error(err);
          })

          如果有多個異步處理就會需要寫多少個 then 來處理異步之間可能存在的同步關(guān)系,從以上的代碼可以看到 then 的處理是一層一層的嵌套。如果換成 co,在寫法上更優(yōu)雅也更符合日常同步編程的寫法:

          co(function* ({
            try {
              var result1 = yield Promise.resolve(1111)
              // 處理第一個異步的結(jié)果
              console.log(result1);
              // 返回第二個異步
              var result2 = yield Promise.resolve(2222)
              // 處理第二個異步的結(jié)果
              console.log(result2)
            } catch (err) {
              console.error(err)
            }
          });

          4.2 分析 next 函數(shù)

          源碼的 next 函數(shù)接收一個 gen.next() 返回的對象 ret 作為參數(shù),形如{value: T, done: boolean},next 函數(shù)只有四行代碼。

          第一行:if (ret.done) return resolve(ret.value); 如果 ret.done 為 true,表明 gen 函數(shù)到了結(jié)束狀態(tài),就 resolve(ret.value),返回結(jié)果。

          第二行:var value = toPromise.call(ctx, ret.value); 調(diào)用 toPromise.call(ctx, ret.value) 函數(shù),toPromise 函數(shù)的作用是把 ret.value 轉(zhuǎn)化成 Promise 類型,也就是用 Promise 包裹一層再 return 出去。

          function toPromise(obj{
            // 如果 obj 不存在,直接返回 obj
            if (!obj) return obj;
            // 如果 obj 是 Promise 類型,直接返回 obj
            if (isPromise(obj)) return obj;
            // 如果 obj 是生成器函數(shù)或遍歷器對象, 就遞歸調(diào)用 co 函數(shù)
            if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
            // 如果 obj 是普通的函數(shù)類型,轉(zhuǎn)換成 Promise 類型函數(shù)再返回
            if ('function' == typeof obj) return thunkToPromise.call(this, obj);
            // 如果 obj 是一個數(shù)組, 轉(zhuǎn)換成 Promise 數(shù)組再返回
            if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
            // 如果 obj 是一個對象, 轉(zhuǎn)換成 Promise 對象再返回
            if (isObject(obj)) return objectToPromise.call(this, obj);
            // 其他情況直接返回
            return obj;
          }

          第三行:if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 如果 value 是 Promise 類型,調(diào)用 onFulfilled 或 onRejected,實際上是遞歸調(diào)用了 next 函數(shù)本身,直到 done 狀態(tài)為 true 或 throw error。

          第四行:return onRejected(...) 如果不是 Promise,直接 Rejected。

          5. 實踐

          雖然解讀了 co 的核心代碼,看起來像是懂了,實際上很容易遺忘。為了加深理解,結(jié)合上面的 co 源碼和自己的思路動手實現(xiàn)一個簡略版的 co。

          5.1 模擬請求

          function request({
            return new Promise((resolve) => {
              setTimeout(() => {
                resolve({data'request'});
              }, 1000);
            });
          }
          // 用 yield 獲取 request 的值
          functiongetData({
            yield request()
          }
          var g = getData()
          var {value, done} = g.next()
          // 間隔1s后打印 {data: "request"}
          value.then(res => console.log(res))

          5.2 模擬實現(xiàn)簡版 co

          核心實現(xiàn):

          1)函數(shù)傳參

          2)generator.next 自動執(zhí)行

          function co(gen{
            // 1. 傳參
            var ctx = this;
            const args = Array.prototype.slice.call(arguments1);
            gen = gen.apply(ctx, args);
            return new Promise(function(resolve, reject{
              // 2. 自動執(zhí)行 next
              onFulfilled()
              
              function onFulfilled (res{
                var ret = gen.next(res);
                next(ret);
              }
              
              function next(ret){
                if (ret.done) return resolve(ret.value);
                // 此處只處理 ret.value 是 Promise 對象的情況,其他類型簡略版沒處理
                var promise = ret.value;
                // 自動執(zhí)行
                promise && promise.then(onFulfilled);
              }
            })
          }

          // 執(zhí)行
          co(functiongetData({
            var result = yield request();
            // 1s后打印 {data: "request"}
            console.log(result)
          })

          6. 感想

          對我來說,學(xué)習(xí)一個新的東西(generator)花費的時間遠(yuǎn)遠(yuǎn)大于單純閱讀源碼的時間,因為需要了解它產(chǎn)生的背景,語法,解決的問題以及一些應(yīng)用場景,這樣在閱讀源碼的時候才知道它為什么要這樣寫。

          讀完源碼,我們會發(fā)現(xiàn),其實 co 就是一個自動執(zhí)行 next() 的函數(shù),而且到最后我們會發(fā)現(xiàn) co 的寫法和我們?nèi)粘J褂玫?async/await 的寫法非常相像,因此也不難理解【async/await 實際上是對 generator 封裝的一個語法糖】這句話了。

          // co 寫法
          co(functiongetData({
            var result = yield request();
            // 1s后打印 {data: "request"}
            console.log(result)
          })
          // async await 寫法
          (async function getData({
            var result = await request();
            // 1s后打印 {data: "request"}
            console.log(result)
          })()

          不得不說,閱讀源碼的確是一個開闊視野的好方法,如果不是這次活動,我可能還要晚個大半年才接觸到 generator,接觸協(xié)程的概念,了解到 async/await 實現(xiàn)的原理,希望能夠繼續(xù)堅持下去~


          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  韩国一级毛片 | 日韩一级视频网站 | 青青青国产在线观看 | 亚洲肏逼 | 尻屄视频网站 |