成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊聊JavaScript 異步編程史

開(kāi)發(fā) 前端
JavaScript 的運(yùn)行時(shí)是跑在單線程上的,是基于事件模型來(lái)進(jìn)行異步任務(wù)觸發(fā)的,不需要考慮共享內(nèi)存加鎖的問(wèn)題,綁定的事件會(huì)按照順序齊齊整整的觸發(fā)。要理解 JavaScript 的異步任務(wù),首先就要理解 JavaScript 的事件模型。

[[403161]]

前言

早期的 Web 應(yīng)用中,與后臺(tái)進(jìn)行交互時(shí),需要進(jìn)行 form 表單的提交,然后在頁(yè)面刷新后給用戶反饋結(jié)果。在頁(yè)面刷新過(guò)程中,后臺(tái)會(huì)重新返回一段 HTML 代碼,這段 HTML 中的大部分內(nèi)容與之前頁(yè)面基本相同,這勢(shì)必造成了流量的浪費(fèi),而且一來(lái)一回也延長(zhǎng)了頁(yè)面的響應(yīng)時(shí)間,總是會(huì)讓人覺(jué)得 Web 應(yīng)用的體驗(yàn)感比不上客戶端應(yīng)用。

2004 年,AJAX 即“Asynchronous JavaScript and XML”技術(shù)橫空出世,讓 Web 應(yīng)用的體驗(yàn)得到了質(zhì)的提升。再到 2006 年,jQuery 問(wèn)世,將 Web 應(yīng)用的開(kāi)發(fā)體驗(yàn)也提高到了新的臺(tái)階。

由于 JavaScript 語(yǔ)言單線程的特點(diǎn),不管是事件的觸發(fā)還是 AJAX 都是通過(guò)回調(diào)的方式進(jìn)行異步任務(wù)的觸發(fā)。如果我們想要線性的處理多個(gè)異步任務(wù),在代碼中就會(huì)出現(xiàn)如下的情況:

  1. getUser(token, function (user) { 
  2.   getClassID(userfunction (id) { 
  3.     getClassName(id, function (name) { 
  4.       console.log(name
  5.     }) 
  6.   }) 
  7. }) 

我們經(jīng)常將這種代碼稱為:“回調(diào)地獄”。

事件與回調(diào)

眾所周知,JavaScript 的運(yùn)行時(shí)是跑在單線程上的,是基于事件模型來(lái)進(jìn)行異步任務(wù)觸發(fā)的,不需要考慮共享內(nèi)存加鎖的問(wèn)題,綁定的事件會(huì)按照順序齊齊整整的觸發(fā)。要理解 JavaScript 的異步任務(wù),首先就要理解 JavaScript 的事件模型。

由于是異步任務(wù),我們需要組織一段代碼放到未來(lái)運(yùn)行(指定時(shí)間結(jié)束時(shí)或者事件觸發(fā)時(shí)),這一段代碼我們通常放到一個(gè)匿名函數(shù)中,通常稱為回調(diào)函數(shù)。

  1. setTimeout(function () { 
  2.   // 在指定時(shí)間結(jié)束時(shí),觸發(fā)的回調(diào) 
  3. }, 800) 
  4. window.addEventListener("resize"function() { 
  5.   // 當(dāng)瀏覽器視窗發(fā)生變化時(shí),觸發(fā)的回調(diào) 
  6. }) 

未來(lái)運(yùn)行

前面說(shuō)過(guò)回調(diào)函數(shù)的運(yùn)行是在未來(lái),這就說(shuō)明回調(diào)中使用的變量并不是在回調(diào)聲明階段就固定的。

  1. for (var i = 0; i < 3; i++) { 
  2.   setTimeout(function () { 
  3.     console.log("i =", i) 
  4.   }, 100) 

這里連續(xù)聲明了三個(gè)異步任務(wù),100毫秒 后會(huì)輸出變量 i 的結(jié)果,按照正常的邏輯應(yīng)該會(huì)輸出 0、1、2這三個(gè)結(jié)果。

然而,事實(shí)并非如此,這也是我們剛開(kāi)始接觸 JavaScript 的時(shí)候會(huì)遇到的問(wèn)題,因?yàn)榛卣{(diào)函數(shù)的實(shí)際運(yùn)行時(shí)機(jī)是在未來(lái),所以輸出的 i 的值是循環(huán)結(jié)束時(shí)的值,三個(gè)異步任務(wù)的結(jié)果一致,會(huì)輸出三個(gè) i = 3。

經(jīng)歷過(guò)這個(gè)問(wèn)題的同學(xué),一般都知道,我們可以通過(guò)閉包的方式,或者重新聲明局部變量的方式解決這個(gè)問(wèn)題。

事件隊(duì)列

事件綁定之后,會(huì)將所有的回調(diào)函數(shù)存儲(chǔ)起來(lái),然后在運(yùn)行過(guò)程中,會(huì)有另外的線程對(duì)這些異步調(diào)用的回調(diào)進(jìn)行調(diào)度的處理,一旦滿足“觸發(fā)”條件就會(huì)將回調(diào)函數(shù)放入到對(duì)應(yīng)的事件隊(duì)列(這里只是簡(jiǎn)單的理解成一個(gè)隊(duì)列,實(shí)際存在兩個(gè)事件隊(duì)列:宏任務(wù)、微任務(wù))中。

滿足觸發(fā)條件一般有以下幾種情況:

  1. DOM 相關(guān)的操作進(jìn)行的事件觸發(fā),比如點(diǎn)擊、移動(dòng)、失焦等行為;
  2. IO 相關(guān)的操作,文件讀取完成、網(wǎng)絡(luò)請(qǐng)求結(jié)束等;
  3. 時(shí)間相關(guān)的操作,到達(dá)定時(shí)任務(wù)的約定時(shí)間;

上面的這些行為發(fā)生時(shí),代碼中之前指定的回調(diào)函數(shù)就會(huì)被放入一個(gè)任務(wù)隊(duì)列中,主線程一旦空閑,就會(huì)將其中的任務(wù)按照先進(jìn)先出的流程一一執(zhí)行。當(dāng)有新的事件被觸發(fā)時(shí),又會(huì)重新放入到回調(diào)中,如此循環(huán)🔄,所以 JavaScript 的這一機(jī)制通常被稱為“事件循環(huán)機(jī)制”。

  1. for (var i = 1; i <= 3; i++) { 
  2.   const x = i 
  3.   setTimeout(function () { 
  4.     console.log(`第${x}個(gè)setTimout被執(zhí)行`) 
  5.   }, 100) 

可以看到,其運(yùn)行順序滿足隊(duì)列先進(jìn)先出的特點(diǎn),先聲明的先被執(zhí)行。

線程的阻塞

由于 JavaScript 單線程的特點(diǎn),定時(shí)器其實(shí)并不可靠,當(dāng)代碼遇到阻塞的情況,即使事件到達(dá)了觸發(fā)的時(shí)間,也會(huì)一直等在主線程空閑才會(huì)運(yùn)行。

  1. const start = Date.now() 
  2. setTimeout(function () { 
  3.   console.log(`實(shí)際等待時(shí)間: ${Date.now() - start}ms`) 
  4. }, 300) 
  5.  
  6. // while循環(huán)讓線程阻塞 800ms 
  7. while(Date.now() - start < 800) {} 

上面代碼中,定時(shí)器設(shè)置了 300ms 后觸發(fā)回調(diào)函數(shù),如果代碼沒(méi)有遇到阻塞,正常情況下會(huì) 300ms后,會(huì)輸出等待時(shí)間。

但是我們?cè)谶€沒(méi)加了一個(gè) while 循環(huán),這個(gè)循環(huán)會(huì)在 800ms 后才結(jié)束,主線程一直被這個(gè)循環(huán)阻塞在這里,導(dǎo)致時(shí)間到了回調(diào)函數(shù)也沒(méi)有正常運(yùn)行。

Promise

事件回調(diào)的方式,在編碼的過(guò)程中,就特別容易造成回調(diào)地獄。而 Promise 提供了一種更加線性的方式編寫異步代碼,有點(diǎn)類似于管道的機(jī)制。

  1. // 回調(diào)地獄 
  2. getUser(token, function (user) { 
  3.   getClassID(userfunction (id) { 
  4.     getClassName(id, function (name) { 
  5.       console.log(name
  6.     }) 
  7.   }) 
  8. }) 
  9.  
  10. // Promise 
  11. getUser(token).then(function (user) { 
  12.   return getClassID(user
  13. }).then(function (id) { 
  14.   return getClassName(id) 
  15. }).then(function (name) { 
  16.   console.log(name
  17. }).catch(function (err) { 
  18.   console.error('請(qǐng)求異常', err) 
  19. }) 

Promise 在很多語(yǔ)言中都有類似的實(shí)現(xiàn),在 JavaScript 發(fā)展過(guò)程中,比較著名的框架 jQuery、Dojo 也都進(jìn)行過(guò)類似的實(shí)現(xiàn)。2009 年,推出的 CommonJS 規(guī)范中,基于 Dojo.Deffered 的實(shí)現(xiàn)方式,提出 Promise/A 規(guī)范。也是這一年 Node.js 橫空出世,Node.js 很多實(shí)現(xiàn)都是依照 CommonJS 規(guī)范來(lái)的,比較熟悉的就是其模塊化方案。

早期的 Node.js 中也實(shí)現(xiàn)了 Promise 對(duì)象,但是 2010 年的時(shí)候,Ry(Node.js 作者)認(rèn)為 Promise 是一種比較上層的實(shí)現(xiàn),而且 Node.js 的開(kāi)發(fā)本來(lái)就依賴于 V8 引擎,V8 引擎原生也沒(méi)有提供 Promise 的支持,所以后來(lái) Node.js 的模塊使用了 error-first callback 的風(fēng)格(cb(error, result))。

  1. const fs = require('fs'
  2. // 第一個(gè)參數(shù)為 Error 對(duì)象,如果不為空,則表示出現(xiàn)異常 
  3. fs.readFile('./README.txt'function (err, buffer) { 
  4.   if (err !== null) { 
  5.     return 
  6.   } 
  7.   console.log(buffer.toString()) 
  8. }) 

這一決定也導(dǎo)致后來(lái) Node.js 中出現(xiàn)了各式各樣的 Promise 類庫(kù),比較出名的就是 Q.js、Bluebird。關(guān)于 Promise 的實(shí)現(xiàn),之前有寫過(guò)一篇文章,感興趣可以看看:《手把手教你實(shí)現(xiàn) Promise》。

在 Node.js@8 之前,V8 原生的 Promise 實(shí)現(xiàn)有一些性能問(wèn)題,導(dǎo)致原生 Promise 的性能甚至不如一些第三方的 Promise 庫(kù)。

所以,低版本的 Node.js 項(xiàng)目中,經(jīng)常會(huì)將 Promise 進(jìn)行全局的替換:

  1. const Bulebird = require('bluebird'
  2. global.Promise = Bulebird 

Generator & co

Generator(生成器) 是 ES6 提供的一種新的函數(shù)類型,主要是用于定義一個(gè)能自我迭代的函數(shù)。通過(guò) function * 的語(yǔ)法能夠構(gòu)造一個(gè) Generator 函數(shù),函數(shù)執(zhí)行后會(huì)返回一個(gè)iteration(迭代器)對(duì)象,該對(duì)象具有一個(gè) next() 方法,每次調(diào)用 next() 方法就會(huì)在 yield 關(guān)鍵詞前面暫停,直到再次調(diào)用 next() 方法。

  1. function * forEach(array) { 
  2.   const len = array.length 
  3.   for (let i = 0; i < len; i ++) { 
  4.     yield i; 
  5.   } 
  6. const it = forEach([2, 4, 6]) 
  7. it.next() // { value: 2, done: false } 
  8. it.next() // { value: 4, done: false } 
  9. it.next() // { value: 6, done: false } 
  10. it.next() // { value: undefined, done: true } 

next() 方法會(huì)返回一個(gè)對(duì)象,對(duì)象有兩個(gè)屬性 value、done:

  • value:表示 yield 后面的值;
  • done:表示函數(shù)是否執(zhí)行完畢;

由于生成器函數(shù)具有中斷執(zhí)行的特點(diǎn),將生成器函數(shù)當(dāng)做一個(gè)異步操作的容器,再配合上 Promise 對(duì)象的 then 方法可以將交回異步邏輯的執(zhí)行權(quán),在每個(gè) yeild 后面都加上一個(gè) Promise 對(duì)象,就能讓迭代器不停的往下執(zhí)行。

  1. function * gen(token) { 
  2.   const user = yield getUser(token) 
  3.   const cId = yield getClassID(user
  4.   const name = yield getClassName(cId) 
  5.   console.log(name
  6.  
  7. const g = gen('xxxx-token'
  8.  
  9. // 執(zhí)行 next 方法返回的 value 為一個(gè) Promise 對(duì)象 
  10. const { value: promise1 } = g.next() 
  11. promise1.then(user => { 
  12.   // 傳入第二個(gè) next 方法的值,會(huì)被生成器中第一個(gè) yield 關(guān)鍵詞前面的變量接受 
  13.   // 往后推也是如此,第三個(gè) next 方法的值,會(huì)被第二個(gè) yield 前面的變量接受 
  14.   // 只有第一個(gè) next 方法的值會(huì)被拋棄 
  15.   const { value: promise2 } = gen.next(user).value 
  16.   promise2.then(cId => { 
  17.     const { value: promise3, done } = gen.next(cId).value 
  18.     // 依次先后傳遞,直到 next 方法返回的 done 為 true 
  19.   }) 
  20. }) 

我們將上面的邏輯進(jìn)行一下抽象,讓每個(gè) Promise 對(duì)象正常返回后,就自動(dòng)調(diào)用 next,讓迭代器進(jìn)行自執(zhí)行,直到執(zhí)行完畢(也就是 done 為 true)。

  1. function co(gen, ...args) { 
  2.   const g = gen(...args) 
  3.   function next(data) { 
  4.     const { value: promise, done } = g.next(data) 
  5.     if (done) return promise 
  6.     promise.then(res => { 
  7.       next(res) // 將 promise 的結(jié)果傳入下一個(gè) yield 
  8.     }) 
  9.   } 
  10.    
  11.   next() // 開(kāi)始自執(zhí)行 
  12.  
  13. co(gen, 'xxxx-token'

這也就是 koa 早期的核心庫(kù) co 的實(shí)現(xiàn)邏輯,只是 co 進(jìn)行了一些參數(shù)校驗(yàn)與錯(cuò)誤處理。通過(guò) generator 加上 co 能夠讓異步流程更加的簡(jiǎn)單易讀,對(duì)開(kāi)發(fā)者而言肯定是階段歡喜的一件事。

async/await

async/await 可以說(shuō)是 JavaScript 異步變成的解決方案,其實(shí)本質(zhì)上就是 Generator & co 的一個(gè)語(yǔ)法糖,只需要在異步的生成器函數(shù)前加上 async,然后將生成器函數(shù)內(nèi)的 yield 替換為 await。

  1. async function fun(token) { 
  2.   const user = await getUser(token) 
  3.   const cId = await getClassID(user
  4.   const name = await getClassName(cId) 
  5.   console.log(name
  6.  
  7. fun() 

 async 函數(shù)將自執(zhí)行器進(jìn)行了內(nèi)置,同時(shí) await 后不限制為 Promise 對(duì)象,可以為任意值,而且 async/await 在語(yǔ)義上比起生成器的 yield 更加清楚,一眼就能明白這是一個(gè)異步操作。

 

責(zé)任編輯:姜華 來(lái)源: 自然醒的筆記本
相關(guān)推薦

2020-10-15 13:29:57

javascript

2015-04-22 10:50:18

JavascriptJavascript異

2014-05-23 10:12:20

Javascript異步編程

2017-07-13 12:12:19

前端JavaScript異步編程

2016-09-07 20:43:36

Javascript異步編程

2021-10-22 08:29:14

JavaScript事件循環(huán)

2011-11-11 15:47:22

JavaScript

2025-02-06 16:51:30

2021-12-10 07:47:30

Javascript異步編程

2022-07-01 08:00:44

異步編程FutureTask

2023-12-04 13:22:00

JavaScript異步編程

2011-11-10 10:23:56

Jscex

2021-06-06 19:51:07

JavaScript異步編程

2011-07-27 14:10:43

javascript

2022-10-31 09:00:24

Promise數(shù)組參數(shù)

2023-11-29 07:10:50

python協(xié)程異步編程

2016-10-21 11:04:07

JavaScript異步編程原理解析

2013-01-07 10:44:00

JavaScriptjQueryJS

2013-04-01 15:38:54

異步編程異步編程模型

2013-03-08 09:33:25

JavaScript同步異步
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久er99热精品一区二区 | 三级黄色片在线观看 | 天天摸天天看 | av网站观看 | 午夜影院在线观看 | 午夜影视网 | 欧美精品一区免费 | 国产精品久久久久久 | 亚洲免费在线观看 | 国产成都精品91一区二区三 | 欧美男人天堂 | 国产99久久 | 日韩中文在线视频 | 午夜精品一区二区三区在线播放 | 国产小视频自拍 | 亚洲美女在线视频 | 亚洲第一天堂 | 精品在线观看一区二区 | 国产精品一区在线观看 | 国产精品国产三级国产aⅴ中文 | 亚洲三级在线观看 | 久久伊人亚洲 | 欧美亚洲视频在线观看 | 老熟女毛片 | 色屁屁在线观看 | 欧美久久一区二区 | 四虎成人精品永久免费av九九 | 怡红院怡春院一级毛片 | www.男人天堂.com | 亚洲精品电影在线观看 | 日本久久网站 | 亚洲精品美女在线观看 | 国产日批 | 成人高清在线 | 免费xxxx大片国产在线 | 国产精品欧美一区喷水 | 99日韩| 美日韩精品 | 国产精品日日摸夜夜添夜夜av | 紧缚调教一区二区三区视频 | 亚洲超碰在线观看 |