欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

ES6 javascript的異步操作實例詳解

 更新時間:2017年10月30日 09:43:27   作者:專注前端30年  
這篇文章主要介紹了ES6 javascript的異步操作,結合實例形式分析了ES5中異步操作的概念、原理、使用方法及相關注意事項,需要的朋友可以參考下

本文實例講述了ES6 javascript的異步操作。分享給大家供大家參考,具體如下:

異步編程對 JavaScript 語言太重要。 Javascript 語言的執(zhí)行環(huán)境是“ 單線程” 的, 如果沒有異步編程, 根本沒法用, 非卡死不可。

ES6 誕生以前, 異步編程的方法, 大概有下面四種。

① 回調(diào)函數(shù)
② 事件監(jiān)聽
③ 發(fā)布 / 訂閱
④ Promise 對象

ES6 將 JavaScript 異步編程帶入了一個全新的階段, ES7 的Async函數(shù)更是提出了異步編程的終極解決方案。

一、基本概念

1. 異步

所謂 " 異步 ",簡單說就是一個任務分成兩段, 先執(zhí)行第一段, 然后轉而執(zhí)行其他任務, 等做好了準備, 再回過頭執(zhí)行第二段。

比如, 有一個任務是讀取文件進行處理, 任務的第一段是向操作系統(tǒng)發(fā)出請求, 要求讀取文件。 然后, 程序執(zhí)行其他任務, 等到操作系統(tǒng)返回文件,再接著執(zhí)行任務的第二段( 處理文件)。 這種不連續(xù)的執(zhí)行, 就叫做異步。

相應地, 連續(xù)的執(zhí)行就叫做同步。 由于是連續(xù)執(zhí)行, 不能插入其他任務, 所以操作系統(tǒng)從硬盤讀取文件的這段時間, 程序只能干等著。

2. 回調(diào)函數(shù)

JavaScript 語言對異步編程的實現(xiàn), 就是回調(diào)函數(shù)。 所謂回調(diào)函數(shù), 就是把任務的第二段單獨寫在一個函數(shù)里面, 等到重新執(zhí)行這個任務的時候, 就直接調(diào)用這個函數(shù)。 它的英語名字 callback, 直譯過來就是 " 重新調(diào)用 "。

讀取文件進行處理, 是這樣寫的。

fs.readFile('/etc/passwd', function(err, data) {
  if(err) throw err;
  console.log(data);
});

上面代碼中, readFile 函數(shù)的第二個參數(shù), 就是回調(diào)函數(shù), 也就是任務的第二段。 等到操作系統(tǒng)返回了 / etc / passwd這個文件以后, 回調(diào)函數(shù)才會執(zhí)行。

一個有趣的問題是, 為什么 Node.js 約定, 回調(diào)函數(shù)的第一個參數(shù), 必須是錯誤對象 err( 如果沒有錯誤, 該參數(shù)就是 null)? 原因是執(zhí)行分成兩段, 在這兩段之間拋出的錯誤, 程序無法捕捉, 只能當作參數(shù), 傳入第二段。

3. Promise

回調(diào)函數(shù)本身并沒有問題, 它的問題出現(xiàn)在多個回調(diào)函數(shù)嵌套。 假定讀取 A 文件之后, 再讀取 B 文件, 代碼如下。

fs.readFile(fileA, function(err, data) {
  fs.readFile(fileB, function(err, data) {
    // ...
  });
});

不難想象, 如果依次讀取多個文件, 就會出現(xiàn)多重嵌套。 代碼不是縱向發(fā)展, 而是橫向發(fā)展, 很快就會亂成一團, 無法管理。 這種情況就稱為 " 回調(diào)函數(shù)噩夢 " ( callback hell )。

Promise 就是為了解決這個問題而提出的。 它不是新的語法功能, 而是一種新的寫法, 允許將回調(diào)函數(shù)的嵌套, 改成鏈式調(diào)用。 采用 Promise, 連續(xù)讀取多個文件, 寫法如下。

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 函數(shù)。 Promise 提供 then 方法加載回調(diào)函數(shù),catch 方法捕捉執(zhí)行過程中拋出的錯誤。
可以看到, Promise 的寫法只是回調(diào)函數(shù)的改進, 使用 then 方法以后, 異步任務的兩段執(zhí)行看得更清楚了, 除此以外, 并無新意。
Promise 的最大問題是代碼冗余, 原來的任務被 Promise 包裝了一下, 不管什么操作, 一眼看去都是一堆 then, 原來的語義變得很不清楚。

那么, 有沒有更好的寫法呢?

二、Generator 函數(shù)

1. 協(xié)程

傳統(tǒng)的編程語言, 早有異步編程的解決方案( 其實是多任務的解決方案)。 其中有一種叫做 " 協(xié)程 "(coroutine), 意思是多個線程互相協(xié)作, 完成異步任務。

協(xié)程有點像函數(shù), 又有點像線程。 它的運行流程大致如下。

第一步, 協(xié)程 A 開始執(zhí)行。
第二步, 協(xié)程 A 執(zhí)行到一半, 進入暫停, 執(zhí)行權轉移到協(xié)程 B。
第三步,( 一段時間后) 協(xié)程 B 交還執(zhí)行權。
第四步, 協(xié)程 A 恢復執(zhí)行。

上面流程的協(xié)程 A, 就是異步任務, 因為它分成兩段( 或多段) 執(zhí)行。

舉例來說, 讀取文件的協(xié)程寫法如下。

function* asyncJob() {
  // ... 其他代碼
  var f = yield readFile(fileA);
  // ... 其他代碼
}

上面代碼的函數(shù)asyncJob是一個協(xié)程, 它的奧妙就在其中的yield命令。 它表示執(zhí)行到此處, 執(zhí)行權將交給其他協(xié)程。 也就是說, yield命令是異步兩個階段的分界線。

協(xié)程遇到y(tǒng)ield命令就暫停, 等到執(zhí)行權返回, 再從暫停的地方繼續(xù)往后執(zhí)行。 它的最大優(yōu)點, 就是代碼的寫法非常像同步操作, 如果去除 yield 命令,簡直一模一樣。

2. Generator 函數(shù)的概念

enerator 函數(shù)是協(xié)程在 ES6 的實現(xiàn), 最大特點就是可以交出函數(shù)的執(zhí)行權( 即暫停執(zhí)行)。

整個 Generator 函數(shù)就是一個封裝的異步任務, 或者說是異步任務的容器。 異步操作需要暫停的地方, 都用yield語句注明。 Generator 函數(shù)的執(zhí)行方法

如下。

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 }

上面代碼中, 調(diào)用 Generator 函數(shù), 會返回一個內(nèi)部指針( 即遍歷器) g。 這是 Generator 函數(shù)不同于普通函數(shù)的另一個地方, 即執(zhí)行它不會返回結果, 返回的是指針對象。 調(diào)用指針 g 的 next 方法, 會移動內(nèi)部指針( 即執(zhí)行異步任務的第一段), 指向第一個遇到的 yield 語句, 上例是執(zhí)行到x + 2 為止。

換言之, next 方法的作用是分階段執(zhí)行 Generator 函數(shù)。 每次調(diào)用 next 方法, 會返回一個對象, 表示當前階段的信息( value 屬性和 done 屬性)。 value屬性是 yield 語句后面表達式的值, 表示當前階段的值; done 屬性是一個布爾值, 表示 Generator 函數(shù)是否執(zhí)行完畢, 即是否還有下一個階段。

3. Generator 函數(shù)的數(shù)據(jù)交換和錯誤處理

Generator 函數(shù)可以暫停執(zhí)行和恢復執(zhí)行, 這是它能封裝異步任務的根本原因。 除此之外, 它還有兩個特性, 使它可以作為異步編程的完整解決方案:函數(shù)體內(nèi)外的數(shù)據(jù)交換和錯誤處理機制。

next 方法返回值的 value 屬性, 是 Generator 函數(shù)向外輸出數(shù)據(jù); next 方法還可以接受參數(shù), 這是向 Generator 函數(shù)體內(nèi)輸入數(shù)據(jù)。

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 方法帶有參數(shù) 2, 這個參數(shù)可以傳入 Generator 函數(shù), 作為上個階段異步任務的返回結果, 被函數(shù)體內(nèi)的變量 y 接收。 因此, 這一步的 value 屬性, 返回的就是 2( 變量 y 的值)。

Generator 函數(shù)內(nèi)部還可以部署錯誤處理代碼, 捕獲函數(shù)體外拋出的錯誤。

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 函數(shù)體外, 使用指針對象的throw 方法拋出的錯誤, 可以被函數(shù)體內(nèi)的try...catch 代碼塊捕獲。 這意味著, 出錯的代碼與處理錯誤的代碼, 實現(xiàn)了時間和空間上的分離, 這對于異步編程無疑是很重要的。

4. 異步任務的封裝

下面看看如何使用 Generator 函數(shù), 執(zhí)行一個真實的異步任務。

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 函數(shù)封裝了一個異步操作, 該操作先讀取一個遠程接口, 然后從 JSON 格式的數(shù)據(jù)解析信息。 就像前面說過的, 這段代碼非常像同步操作, 除了加上了 yield 命令。

執(zhí)行這段代碼的方法如下。

var g = gen();
var result = g.next();
result.value.then(function(data) {
  return data.json();
}).then(function(data) {
  g.next(data);
});

上面代碼中, 首先執(zhí)行 Generator 函數(shù), 獲取遍歷器對象, 然后使用 next 方法( 第二行), 執(zhí)行異步任務的第一階段。 由于 Fetch 模塊返回的是一個Promise 對象, 因此要用 then 方法調(diào)用下一個 next 方法。

可以看到, 雖然 Generator 函數(shù)將異步操作表示得很簡潔, 但是流程管理卻不方便( 即何時執(zhí)行第一階段、 何時執(zhí)行第二階段)。

更多相關內(nèi)容可查看本站專題:《ECMAScript6(ES6)入門教程》、《JavaScript數(shù)組操作技巧總結》、《JavaScript字符與字符串操作技巧總結》、《JavaScript數(shù)據(jù)結構與算法技巧總結》、《JavaScript錯誤與調(diào)試技巧總結》及《javascript面向對象入門教程

希望本文所述對大家基于ECMAScript的程序設計有所幫助。

相關文章

最新評論