您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“ES6中javascript如何實現異步操作”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“ES6中javascript如何實現異步操作”這篇文章吧。
具體如下:
異步編程對 JavaScript 語言太重要。 Javascript 語言的執行環境是“ 單線程” 的, 如果沒有異步編程, 根本沒法用, 非卡死不可。
ES6 誕生以前, 異步編程的方法, 大概有下面四種。
① 回調函數
② 事件監聽
③ 發布 / 訂閱
④ Promise 對象
ES6 將 JavaScript 異步編程帶入了一個全新的階段, ES7 的Async函數更是提出了異步編程的終極解決方案。
一、基本概念
1. 異步
所謂 " 異步 ",簡單說就是一個任務分成兩段, 先執行第一段, 然后轉而執行其他任務, 等做好了準備, 再回過頭執行第二段。
比如, 有一個任務是讀取文件進行處理, 任務的第一段是向操作系統發出請求, 要求讀取文件。 然后, 程序執行其他任務, 等到操作系統返回文件,再接著執行任務的第二段( 處理文件)。 這種不連續的執行, 就叫做異步。
相應地, 連續的執行就叫做同步。 由于是連續執行, 不能插入其他任務, 所以操作系統從硬盤讀取文件的這段時間, 程序只能干等著。
2. 回調函數
JavaScript 語言對異步編程的實現, 就是回調函數。 所謂回調函數, 就是把任務的第二段單獨寫在一個函數里面, 等到重新執行這個任務的時候, 就直接調用這個函數。 它的英語名字 callback, 直譯過來就是 " 重新調用 "。
讀取文件進行處理, 是這樣寫的。
fs.readFile('/etc/passwd', function(err, data) { if(err) throw err; console.log(data); });
上面代碼中, readFile 函數的第二個參數, 就是回調函數, 也就是任務的第二段。 等到操作系統返回了 / etc / passwd這個文件以后, 回調函數才會執行。
一個有趣的問題是, 為什么 Node.js 約定, 回調函數的第一個參數, 必須是錯誤對象 err( 如果沒有錯誤, 該參數就是 null)? 原因是執行分成兩段, 在這兩段之間拋出的錯誤, 程序無法捕捉, 只能當作參數, 傳入第二段。
3. Promise
回調函數本身并沒有問題, 它的問題出現在多個回調函數嵌套。 假定讀取 A 文件之后, 再讀取 B 文件, 代碼如下。
fs.readFile(fileA, function(err, data) { fs.readFile(fileB, function(err, data) { // ... }); });
不難想象, 如果依次讀取多個文件, 就會出現多重嵌套。 代碼不是縱向發展, 而是橫向發展, 很快就會亂成一團, 無法管理。 這種情況就稱為 " 回調函數噩夢 " ( callback hell )。
Promise 就是為了解決這個問題而提出的。 它不是新的語法功能, 而是一種新的寫法, 允許將回調函數的嵌套, 改成鏈式調用。 采用 Promise, 連續讀取多個文件, 寫法如下。
var readFile = require('fs-readfile-promise'); readFile(fileA) .then(function(data) { console.log(data.toString()); }) .then(function() { return readFile(fileB); }) .then(function(data) { console.log(data.toString()); }) .catch(function(err) { console.log(err); });
上面代碼中, 我使用了 fs - readfile - promise 模塊, 它的作用就是返回一個 Promise 版本的 readFile 函數。 Promise 提供 then 方法加載回調函數,catch 方法捕捉執行過程中拋出的錯誤。
可以看到, Promise 的寫法只是回調函數的改進, 使用 then 方法以后, 異步任務的兩段執行看得更清楚了, 除此以外, 并無新意。
Promise 的最大問題是代碼冗余, 原來的任務被 Promise 包裝了一下, 不管什么操作, 一眼看去都是一堆 then, 原來的語義變得很不清楚。
那么, 有沒有更好的寫法呢?
二、Generator 函數
1. 協程
傳統的編程語言, 早有異步編程的解決方案( 其實是多任務的解決方案)。 其中有一種叫做 " 協程 "(coroutine), 意思是多個線程互相協作, 完成異步任務。
協程有點像函數, 又有點像線程。 它的運行流程大致如下。
第一步, 協程 A 開始執行。
第二步, 協程 A 執行到一半, 進入暫停, 執行權轉移到協程 B。
第三步,( 一段時間后) 協程 B 交還執行權。
第四步, 協程 A 恢復執行。
上面流程的協程 A, 就是異步任務, 因為它分成兩段( 或多段) 執行。
舉例來說, 讀取文件的協程寫法如下。
function* asyncJob() { // ... 其他代碼 var f = yield readFile(fileA); // ... 其他代碼 }
上面代碼的函數asyncJob是一個協程, 它的奧妙就在其中的yield命令。 它表示執行到此處, 執行權將交給其他協程。 也就是說, yield命令是異步兩個階段的分界線。
協程遇到yield命令就暫停, 等到執行權返回, 再從暫停的地方繼續往后執行。 它的最大優點, 就是代碼的寫法非常像同步操作, 如果去除 yield 命令,簡直一模一樣。
2. Generator 函數的概念
enerator 函數是協程在 ES6 的實現, 最大特點就是可以交出函數的執行權( 即暫停執行)。
整個 Generator 函數就是一個封裝的異步任務, 或者說是異步任務的容器。 異步操作需要暫停的地方, 都用yield語句注明。 Generator 函數的執行方法
如下。
function* gen(x) { var y = yield x + 2; return y; } var g = gen(1); g.next() // { value: 3, done: false } g.next() // { value: undefined, done: true }
上面代碼中, 調用 Generator 函數, 會返回一個內部指針( 即遍歷器) g。 這是 Generator 函數不同于普通函數的另一個地方, 即執行它不會返回結果, 返回的是指針對象。 調用指針 g 的 next 方法, 會移動內部指針( 即執行異步任務的第一段), 指向第一個遇到的 yield 語句, 上例是執行到x + 2 為止。
換言之, next 方法的作用是分階段執行 Generator 函數。 每次調用 next 方法, 會返回一個對象, 表示當前階段的信息( value 屬性和 done 屬性)。 value屬性是 yield 語句后面表達式的值, 表示當前階段的值; done 屬性是一個布爾值, 表示 Generator 函數是否執行完畢, 即是否還有下一個階段。
3. Generator 函數的數據交換和錯誤處理
Generator 函數可以暫停執行和恢復執行, 這是它能封裝異步任務的根本原因。 除此之外, 它還有兩個特性, 使它可以作為異步編程的完整解決方案:函數體內外的數據交換和錯誤處理機制。
next 方法返回值的 value 屬性, 是 Generator 函數向外輸出數據; next 方法還可以接受參數, 這是向 Generator 函數體內輸入數據。
function* gen(x) { var y = yield x + 2; return y; } var g = gen(1); g.next() // { value: 3, done: false } g.next(2) // { value: 2, done: true }
上面代碼中, 第一個 next 方法的 value 屬性, 返回表達式x + 2 的值( 3)。 第二個 next 方法帶有參數 2, 這個參數可以傳入 Generator 函數, 作為上個階段異步任務的返回結果, 被函數體內的變量 y 接收。 因此, 這一步的 value 屬性, 返回的就是 2( 變量 y 的值)。
Generator 函數內部還可以部署錯誤處理代碼, 捕獲函數體外拋出的錯誤。
function* gen(x) { try { var y = yield x + 2; } catch(e) { console.log(e); } return y; } var g = gen(1); g.next(); g.throw(' 出錯了 ');
上面代碼的最后一行, Generator 函數體外, 使用指針對象的throw 方法拋出的錯誤, 可以被函數體內的try...catch 代碼塊捕獲。 這意味著, 出錯的代碼與處理錯誤的代碼, 實現了時間和空間上的分離, 這對于異步編程無疑是很重要的。
4. 異步任務的封裝
下面看看如何使用 Generator 函數, 執行一個真實的異步任務。
var fetch = require('node-fetch'); function* gen() { var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); }
上面代碼中, Generator 函數封裝了一個異步操作, 該操作先讀取一個遠程接口, 然后從 JSON 格式的數據解析信息。 就像前面說過的, 這段代碼非常像同步操作, 除了加上了 yield 命令。
執行這段代碼的方法如下。
var g = gen(); var result = g.next(); result.value.then(function(data) { return data.json(); }).then(function(data) { g.next(data); });
上面代碼中, 首先執行 Generator 函數, 獲取遍歷器對象, 然后使用 next 方法( 第二行), 執行異步任務的第一階段。 由于 Fetch 模塊返回的是一個Promise 對象, 因此要用 then 方法調用下一個 next 方法。
可以看到, 雖然 Generator 函數將異步操作表示得很簡潔, 但是流程管理卻不方便( 即何時執行第一階段、 何時執行第二階段)。
以上是“ES6中javascript如何實現異步操作”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。