中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript異步編程如何實現

發布時間:2021-06-12 17:34:57 來源:億速云 閱讀:131 作者:Leah 欄目:web開發

本篇文章給大家分享的是有關JavaScript異步編程如何實現,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

事件與回調

眾所周知,JavaScript  的運行時是跑在單線程上的,是基于事件模型來進行異步任務觸發的,不需要考慮共享內存加鎖的問題,綁定的事件會按照順序齊齊整整的觸發。要理解 JavaScript  的異步任務,首先就要理解 JavaScript 的事件模型。

由于是異步任務,我們需要組織一段代碼放到未來運行(指定時間結束時或者事件觸發時),這一段代碼我們通常放到一個匿名函數中,通常稱為回調函數。

setTimeout(function () {   // 在指定時間結束時,觸發的回調 }, 800) window.addEventListener("resize", function() {   // 當瀏覽器視窗發生變化時,觸發的回調 })

未來運行

前面說過回調函數的運行是在未來,這就說明回調中使用的變量并不是在回調聲明階段就固定的。

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

這里連續聲明了三個異步任務,100毫秒 后會輸出變量 i 的結果,按照正常的邏輯應該會輸出 0、1、2這三個結果。

然而,事實并非如此,這也是我們剛開始接觸 JavaScript 的時候會遇到的問題,因為回調函數的實際運行時機是在未來,所以輸出的 i  的值是循環結束時的值,三個異步任務的結果一致,會輸出三個 i = 3。

JavaScript異步編程如何實現

經歷過這個問題的同學,一般都知道,我們可以通過閉包的方式,或者重新聲明局部變量的方式解決這個問題。

事件隊列

事件綁定之后,會將所有的回調函數存儲起來,然后在運行過程中,會有另外的線程對這些異步調用的回調進行調度的處理,一旦滿足“觸發”條件就會將回調函數放入到對應的事件隊列(這里只是簡單的理解成一個隊列,實際存在兩個事件隊列:宏任務、微任務)中。

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

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. DOM 相關的操作進行的事件觸發,比如點擊、移動、失焦等行為;

  3. IO 相關的操作,文件讀取完成、網絡請求結束等;

  4. 時間相關的操作,到達定時任務的約定時間;

上面的這些行為發生時,代碼中之前指定的回調函數就會被放入一個任務隊列中,主線程一旦空閑,就會將其中的任務按照先進先出的流程一一執行。當有新的事件被觸發時,又會重新放入到回調中,如此循環?,所以  JavaScript 的這一機制通常被稱為“事件循環機制”。

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

可以看到,其運行順序滿足隊列先進先出的特點,先聲明的先被執行。

JavaScript異步編程如何實現

線程的阻塞

由于 JavaScript 單線程的特點,定時器其實并不可靠,當代碼遇到阻塞的情況,即使事件到達了觸發的時間,也會一直等在主線程空閑才會運行。

const start = Date.now() setTimeout(function () {   console.log(`實際等待時間: ${Date.now() - start}ms`) }, 300)  // while循環讓線程阻塞 800ms while(Date.now() - start < 800) {}

上面代碼中,定時器設置了 300ms 后觸發回調函數,如果代碼沒有遇到阻塞,正常情況下會 300ms后,會輸出等待時間。

但是我們在還沒加了一個 while 循環,這個循環會在 800ms 后才結束,主線程一直被這個循環阻塞在這里,導致時間到了回調函數也沒有正常運行。

JavaScript異步編程如何實現

Promise

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

// 回調地獄 getUser(token, function (user) {   getClassID(user, function (id) {     getClassName(id, function (name) {       console.log(name)     })   }) })  // Promise getUser(token).then(function (user) {   return getClassID(user) }).then(function (id) {   return getClassName(id) }).then(function (name) {   console.log(name) }).catch(function (err) {   console.error('請求異常', err) })

Promise 在很多語言中都有類似的實現,在 JavaScript 發展過程中,比較著名的框架 jQuery、Dojo 也都進行過類似的實現。2009  年,推出的 CommonJS 規范中,基于 Dojo.Deffered 的實現方式,提出 Promise/A 規范。也是這一年 Node.js  橫空出世,Node.js 很多實現都是依照 CommonJS 規范來的,比較熟悉的就是其模塊化方案。

早期的 Node.js 中也實現了 Promise 對象,但是 2010 年的時候,Ry(Node.js 作者)認為 Promise  是一種比較上層的實現,而且 Node.js 的開發本來就依賴于 V8 引擎,V8 引擎原生也沒有提供 Promise 的支持,所以后來 Node.js  的模塊使用了 error-first callback 的風格(cb(error, result))。

const fs = require('fs') // 第一個參數為 Error 對象,如果不為空,則表示出現異常 fs.readFile('./README.txt', function (err, buffer) {   if (err !== null) {     return   }   console.log(buffer.toString()) })

這一決定也導致后來 Node.js 中出現了各式各樣的 Promise 類庫,比較出名的就是 Q.js、Bluebird。關于 Promise  的實現,之前有寫過一篇文章,感興趣可以看看:《手把手教你實現 Promise》。

在 Node.js@8 之前,V8 原生的 Promise 實現有一些性能問題,導致原生 Promise 的性能甚至不如一些第三方的 Promise  庫。

JavaScript異步編程如何實現

所以,低版本的 Node.js 項目中,經常會將 Promise 進行全局的替換:

const Bulebird = require('bluebird') global.Promise = Bulebird

Generator & co

Generator(生成器) 是 ES6 提供的一種新的函數類型,主要是用于定義一個能自我迭代的函數。通過 function * 的語法能夠構造一個  Generator 函數,函數執行后會返回一個iteration(迭代器)對象,該對象具有一個 next() 方法,每次調用 next() 方法就會在  yield 關鍵詞前面暫停,直到再次調用 next() 方法。

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

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

  • value:表示 yield 后面的值;

  • done:表示函數是否執行完畢;

由于生成器函數具有中斷執行的特點,將生成器函數當做一個異步操作的容器,再配合上 Promise 對象的 then 方法可以將交回異步邏輯的執行權,在每個  yeild 后面都加上一個 Promise 對象,就能讓迭代器不停的往下執行。

function * gen(token) {   const user = yield getUser(token)   const cId = yield getClassID(user)   const name = yield getClassName(cId)   console.log(name) }  const g = gen('xxxx-token')  // 執行 next 方法返回的 value 為一個 Promise 對象 const { value: promise1 } = g.next() promise1.then(user => {   // 傳入第二個 next 方法的值,會被生成器中第一個 yield 關鍵詞前面的變量接受   // 往后推也是如此,第三個 next 方法的值,會被第二個 yield 前面的變量接受   // 只有第一個 next 方法的值會被拋棄   const { value: promise2 } = gen.next(user).value   promise2.then(cId => {     const { value: promise3, done } = gen.next(cId).value     // 依次先后傳遞,直到 next 方法返回的 done 為 true   }) })

我們將上面的邏輯進行一下抽象,讓每個 Promise 對象正常返回后,就自動調用 next,讓迭代器進行自執行,直到執行完畢(也就是 done 為  true)。

function co(gen, ...args) {   const g = gen(...args)   function next(data) {     const { value: promise, done } = g.next(data)     if (done) return promise     promise.then(res => {       next(res) // 將 promise 的結果傳入下一個 yield     })   }      next() // 開始自執行 }  co(gen, 'xxxx-token')

這也就是 koa 早期的核心庫 co 的實現邏輯,只是 co 進行了一些參數校驗與錯誤處理。通過 generator 加上 co  能夠讓異步流程更加的簡單易讀,對開發者而言肯定是階段歡喜的一件事。

async/await

async/await 可以說是 JavaScript 異步變成的解決方案,其實本質上就是 Generator & co  的一個語法糖,只需要在異步的生成器函數前加上 async,然后將生成器函數內的 yield 替換為 await。

async function fun(token) {   const user = await getUser(token)   const cId = await getClassID(user)   const name = await getClassName(cId)   console.log(name) }  fun()

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

以上就是JavaScript異步編程如何實現,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

龙江县| 平阳县| 曲周县| 邯郸市| 阳东县| 固始县| 台中县| 双江| 郧西县| 象山县| 且末县| 长宁县| 赣州市| 岗巴县| 临桂县| 宕昌县| 双鸭山市| 普兰县| 襄垣县| 仲巴县| 西乡县| 房山区| 松江区| 海阳市| 洪江市| 鹤山市| 济宁市| 平南县| 华容县| 金山区| 磐石市| 沅江市| 新田县| 彝良县| 安丘市| 宿迁市| 饶阳县| 江川县| 万山特区| 乌鲁木齐市| 哈巴河县|