成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 與 異步編程

YFan / 2118人閱讀

摘要:然而異步編程真正發(fā)展壯大,的流行功不可沒。于是從異步編程誕生的那一刻起,它就和回調(diào)函數(shù)綁在了一起。這個(gè)函數(shù)會(huì)起一個(gè)定時(shí)器,在超過指定時(shí)間后執(zhí)行指定的函數(shù)。我們知道是異步編程的未來。

什么是異步(Asynchrony)

按照維基百科上的解釋:獨(dú)立于主控制流之外發(fā)生的事件就叫做異步。比如說有一段順序執(zhí)行的代碼

void function main() {
  fA();
  fB();
}();

fA => fB 是順序執(zhí)行的,永遠(yuǎn)都是 fAfB 的前面執(zhí)行,他們就是 同步 的關(guān)系。加入這時(shí)使用 setTimeout 將 fA 延后

void function main() {
  setTimeout(fA, 1000);
  fB();
}();

這時(shí),fA 相對于 fB 就是異步的。main 函數(shù)只是聲明了要在一秒后執(zhí)行一次 fA,而并沒有立刻執(zhí)行它。這時(shí),fA 的控制流就獨(dú)立于 main 之外。

JavaScript——天生異步的語言

因?yàn)?setTimeout 的存在,至少在被 ECMA 標(biāo)準(zhǔn)化的那一刻起,JavaScript 就支持異步編程了。與其他語言的 sleep 不同,setTimeout 是異步的——它不會(huì)阻擋當(dāng)前程序繼續(xù)往下執(zhí)行。

然而異步編程真正發(fā)展壯大,Ajax 的流行功不可沒。Ajax 中的 A(Asynchronous)真正點(diǎn)到了異步的概念——這還是 IE5、IE6 的時(shí)代。

回調(diào)函數(shù)——異步編程之痛

異步任務(wù)執(zhí)行完畢之后怎樣通知開發(fā)者呢?回調(diào)函數(shù)是最樸素的,容易想到的實(shí)現(xiàn)方式。于是從異步編程誕生的那一刻起,它就和回調(diào)函數(shù)綁在了一起。

例如 setTimeout。這個(gè)函數(shù)會(huì)起一個(gè)定時(shí)器,在超過指定時(shí)間后執(zhí)行指定的函數(shù)。比如在一秒后輸出數(shù)字 1,代碼如下:

setTimeout(() => {
  console.log(1);
}, 1000);

常規(guī)用法。如果需求有變,需要每秒輸出一個(gè)數(shù)字(當(dāng)然不是用 setInterval),JavaScript 的初學(xué)者可能會(huì)寫出這樣的代碼:

for (let i = 1; i < 10; ++i) {
  setTimeout(() => { // 錯(cuò)誤!
    console.log(i);
  }, 1000);
}

執(zhí)行結(jié)果是等待 1 秒后,一次性輸出了所有結(jié)果。因?yàn)檫@里的循環(huán)是同時(shí)啟了 10 個(gè)定時(shí)器,每個(gè)定時(shí)器都等待 1 秒,結(jié)果當(dāng)然是所有定時(shí)器在 1 秒后同時(shí)超時(shí),觸發(fā)回調(diào)函數(shù)。

解法也簡單,只需要在前一個(gè)定時(shí)器超時(shí)后再啟動(dòng)另一個(gè)定時(shí)器,代碼如下:

setTimeout(() => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
    setTimeout(() => {
      console.log(3);
      setTimeout(() => {
        console.log(4);
        setTimeout(() => {
          console.log(5);
          setTimeout(() => {
            // ...
          }, 1000);
        }, 1000);
      }, 1000)
    }, 1000)
  }, 1000)
}, 1000);

層層嵌套,結(jié)果就是這樣的漏斗形代碼。可能有人想到了新標(biāo)準(zhǔn)中的 Promise,可以改寫如下:

function timeout(delay) {
  return new Promise(resolve => {
    setTimeout(resolve, delay);
  });
}

timeout(1000).then(() => {
  console.log(1);
  return timeout(1000);
}).then(() => {
  console.log(2);
  return timeout(1000);
}).then(() => {
  console.log(3);
  return timeout(1000);
}).then(() => {
  console.log(4);
  return timeout(1000);
}).then(() => {
  console.log(5);
  return timeout(1000);
}).then(() => {
  // ..
});

漏斗形代碼是沒了,但代碼量本身并沒減少多少。Promise 并沒能干掉回調(diào)函數(shù)。

因?yàn)榛卣{(diào)函數(shù)的存在,循環(huán)就無法使用。不能循環(huán),那么只能考慮遞歸了,解法如下:

let i = 1;
function next() {
  console.log(i);
  if (++i < 10) {
    setTimeout(next, 1000);
  }
}
setTimeout(next, 1000);

注意雖然寫法是遞歸,但由于 next 函數(shù)都是由瀏覽器調(diào)用的,所以實(shí)際上并沒有遞歸函數(shù)的調(diào)用棧結(jié)構(gòu)。

Generator——JavaScript 中的半?yún)f(xié)程

很多語言都引入了協(xié)程來簡化異步編程,JavaScript 也有類似的概念,叫做 Generator。

MDN 上的解釋:Generator 是一種可以中途退出之后重入的函數(shù)。他們的函數(shù)上下文在每次重入后會(huì)被保持。簡而言之,Generator 與普通 Function 最大的區(qū)別就是:Generator 自身保留上次調(diào)用的狀態(tài)。

舉個(gè)簡單的例子:

function *gen() {
  yield 1;
  yield 2;
  return 3;
}

void function main() {
  var iter = gen();
  console.log(iter.next().value);
  console.log(iter.next().value);
  console.log(iter.next().value);
}();

代碼的執(zhí)行順序是這樣:

請求 gen,得到一個(gè)迭代器 iter。注意此時(shí)并未真正執(zhí)行 gen 的函數(shù)體。

調(diào)用 iter.next(),執(zhí)行 gen 的函數(shù)體。

遇到 yield 1,將 1 返回,iter.next() 的返回值即為 { done: false, value: 1 },輸出 1

調(diào)用 iter.next()。從上次 yield 出去的地方繼續(xù)往下執(zhí)行 gen。

遇到 yield 2,將 2 返回,iter.next() 的返回值即為 { done: false, value: 2 },輸出 2

調(diào)用 iter.next()。從上次 yield 出去的地方繼續(xù)往下執(zhí)行 gen

遇到 return 3,將 3 返回,return 表示整個(gè)函數(shù)已經(jīng)執(zhí)行完畢。iter.next() 的返回值即為 { done: true, value: 3 },輸出 3

調(diào)用 Generator 函數(shù)只會(huì)返回一個(gè)迭代器,當(dāng)用戶主動(dòng)調(diào)用了 iter.next() 后,這個(gè) Generator 函數(shù)才會(huì)真正執(zhí)行。

你可以使用 for ... of 遍歷一個(gè) iterator,例如

for (var i of gen()) {
  console.log(i);
}

輸出 1 2,最后 return 3 的結(jié)果不算在內(nèi)。想用 Generator 的各項(xiàng)生成一個(gè)數(shù)組也很簡單,Array.from(gen()) 或直接用 [...gen()] 即可,生成 [1, 2] 同樣不包含最后的 return 3。

Generator 是異步的嗎

Generator 也叫半?yún)f(xié)程(semicoroutine),自然與異步關(guān)系匪淺。那么 Generator 是異步的嗎?

既是也不是。前面提到,異步是相對的,例如上面的例子

function *gen() {
  yield 1;
  yield 2;
  return 3;
}

void function main() {
  var iter = gen();
  console.log(iter.next().value);
  console.log(iter.next().value);
  console.log(iter.next().value);
}();

我們可以很直觀的看到,gen 的方法體與 main 的方法體在交替執(zhí)行,所以可以肯定的說,gen 相對于 main 是異步執(zhí)行的。然而此段過程中,整個(gè)控制流都沒有交回給瀏覽器,所以說 gen 和 main 相對于瀏覽器是同步執(zhí)行的。

用 Generator 簡化異步代碼

回到最初的問題:

for (let i = 0; i < 10; ++i) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
  // 等待上面 setTimeout 執(zhí)行完畢
}

關(guān)鍵在于如何等待前面的 setTimeout 觸發(fā)回調(diào)后再執(zhí)行下一輪循環(huán)。如果使用 Generator,我們可以考慮在 setTimeoutyield 出去(控制流返還給瀏覽器),然后在 setTimeout 觸發(fā)的回調(diào)函數(shù)中 next,將控制流交還回給代碼,執(zhí)行下一段循環(huán)。

let iter;

function* run() {
  for (let i = 1; i < 10; ++i) {
    setTimeout(() => iter.next(), 1000);
    yield; // 等待上面 setTimeout 執(zhí)行完畢
    console.log(i);
  }
}

iter = run();
iter.next();

代碼的執(zhí)行順序是這樣:

請求 run,得到一個(gè)迭代器 iter。注意此時(shí)并未真正執(zhí)行 run 的函數(shù)體。

調(diào)用 iter.next(),執(zhí)行 run 的函數(shù)體。

循環(huán)開始,i 初始化為 1。

執(zhí)行 setTimeout,啟動(dòng)一個(gè)定時(shí)器,回調(diào)函數(shù)延后 1 秒執(zhí)行。

遇到 yield(即 yield undefined),控制流返回到最后的 iter.next() 之后。因?yàn)楹竺鏇]有其他代碼了,瀏覽器獲得控制權(quán),響應(yīng)用戶事件,執(zhí)行其他異步代碼等。

1 秒后,setTimeout 超時(shí),執(zhí)行回調(diào)函數(shù) () => iter.next()。

調(diào)用 iter.next()。從上次 yield 出去的地方繼續(xù)往下執(zhí)行,即 console.log(i),輸出 i 的值。

一次循環(huán)結(jié)束,i 自增為 2,回到第 4 步繼續(xù)執(zhí)行

……

這樣即實(shí)現(xiàn)了類似同步 sleep 的要求。

async、await——用同步語法寫異步代碼

上面的代碼畢竟需要手工定義迭代器變量,還要手工 next;更重要的是與 setTimeout 緊耦合,無法通用。

我們知道 Promise 是異步編程的未來。能不能把 PromiseGenerator 結(jié)合使用呢?這樣考慮的結(jié)果就是 async 函數(shù)。

async 得到代碼如下

function timeout(delay) {
  return new Promise(resolve => {
    setTimeout(resolve, delay);
  });
}

async function run() {
  for (let i = 1; i < 10; ++i) {
    await timeout(1000);
    console.log(i);
  }
}
run();

按照 Chrome 的設(shè)計(jì)文檔,async 函數(shù)內(nèi)部就是被編譯為 Generator 執(zhí)行的。run 函數(shù)本身會(huì)返回一個(gè) Promise,用于使主調(diào)函數(shù)得知 run 函數(shù)什么時(shí)候執(zhí)行完畢。所以 run() 后面也可以 .then(xxx),甚至直接 await run()。

注意有時(shí)候我們的確需要幾個(gè)異步事件并行執(zhí)行(比如調(diào)用兩個(gè)接口,等兩個(gè)接口都返回后執(zhí)行后續(xù)代碼),這時(shí)就不要過度使用 await,例如:

const a = await queryA(); // 等待 queryA 執(zhí)行完畢后
const b = await queryB(); // 執(zhí)行 queryB
doSomething(a, b);

這時(shí) queryAqueryB 就是串行執(zhí)行的。可以略作修改:

const promiseA = queryA(); // 執(zhí)行 queryA
const b = await queryB(); // 執(zhí)行 queryB 并等待其執(zhí)行結(jié)束。這時(shí)同時(shí) queryA 也在執(zhí)行。
const a = await promiseA(); // 這時(shí) queryB 已經(jīng)執(zhí)行結(jié)束。繼續(xù)等待 queryA 執(zhí)行結(jié)束
doSomething(a, b);

我個(gè)人比較喜歡如下寫法:

const [ a, b ] = await Promise.all([ queryA(), queryB() ]);
doSomething(a, b);

awaitPromise 結(jié)合使用,效果更佳!

結(jié)束語

如今 async 函數(shù)已經(jīng)被各大主流瀏覽器實(shí)現(xiàn)(除了 IE)。如果要兼容舊版瀏覽器,可以使用 babel 將其編譯為 Generator。如果還要兼容只支持 ES5 的瀏覽器,還可以繼續(xù)把 Generator 編譯為 ES5。編譯后的代碼量比較大,小心代碼膨脹。

如果是用 node 寫 Server,那就不用糾結(jié)了直接用就是了。koa 是用 async 是你的好幫手。

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88574.html

相關(guān)文章

  • ES6-7

    摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過...

    tuniutech 評論0 收藏0
  • 夯實(shí)基礎(chǔ)-JavaScript異步編程

    摘要:調(diào)用棧被清空,消息隊(duì)列中并無任務(wù),線程停止,事件循環(huán)結(jié)束。不確定的時(shí)間點(diǎn)請求返回,將設(shè)定好的回調(diào)函數(shù)放入消息隊(duì)列。調(diào)用棧執(zhí)行完畢執(zhí)行消息隊(duì)列任務(wù)。請求并發(fā)回調(diào)函數(shù)執(zhí)行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎(chǔ)中的重點(diǎn),也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執(zhí)行的時(shí)候是從上到下按順序執(zhí)行,一段代碼執(zhí)行了之后才會(huì)執(zhí)行下一段代碼,這種方式...

    shadowbook 評論0 收藏0
  • 淺析JavaScript異步

    摘要:回調(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因?yàn)槭录]有被觸發(fā)或者條件不滿足。同步方式請求異步同步請求當(dāng)請求開始發(fā)送時(shí),瀏覽器事件線程通知主線程,讓線程發(fā)送數(shù)據(jù)請求,主線程收到 一直以來都知道JavaScript是一門單線程語言,在筆試過程中不斷的遇到一些輸出結(jié)果的問題,考量的是對異步編程掌握情況。一般被問到異步的時(shí)候腦子里第一反應(yīng)就是Ajax,setTimse...

    Tangpj 評論0 收藏0
  • Javascript中的異步編程

    摘要:接下來,我們一起來看看中的異步編程,具體有哪幾種。實(shí)現(xiàn)異步編程的方法一回調(diào)函數(shù)上面不止一次提到了回調(diào)函數(shù)。它是異步編程中,最基本的方法。四對象接下來,我們聊聊與相關(guān)的異步編程方法,對象。 showImg(https://segmentfault.com/img/bVbneWy?w=1600&h=1200); 前言 最近,小伙伴S 問了我一段代碼: const funB = (value...

    wemall 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<