溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

ES6中javascript如何實(shí)現(xiàn)異步操作

發(fā)布時(shí)間:2021-07-15 14:27:46 來(lái)源:億速云 閱讀:182 作者:小新 欄目:web開發(fā)

這篇文章主要為大家展示了“ES6中javascript如何實(shí)現(xiàn)異步操作”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“ES6中javascript如何實(shí)現(xiàn)異步操作”這篇文章吧。

具體如下:

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

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

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

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

一、基本概念

1. 異步

所謂 " 異步 ",簡(jiǎn)單說(shuō)就是一個(gè)任務(wù)分成兩段, 先執(zhí)行第一段, 然后轉(zhuǎn)而執(zhí)行其他任務(wù), 等做好了準(zhǔn)備, 再回過(guò)頭執(zhí)行第二段。

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

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

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

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

讀取文件進(jìn)行處理, 是這樣寫的。

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

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

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

3. Promise

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

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

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

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

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

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

二、Generator 函數(shù)

1. 協(xié)程

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

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

第一步, 協(xié)程 A 開始執(zhí)行。
第二步, 協(xié)程 A 執(zhí)行到一半, 進(jìn)入暫停, 執(zhí)行權(quán)轉(zhuǎn)移到協(xié)程 B。
第三步,( 一段時(shí)間后) 協(xié)程 B 交還執(zhí)行權(quán)。
第四步, 協(xié)程 A 恢復(fù)執(zhí)行。

上面流程的協(xié)程 A, 就是異步任務(wù), 因?yàn)樗殖蓛啥危?或多段) 執(zhí)行。

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

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

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

協(xié)程遇到y(tǒng)ield命令就暫停, 等到執(zhí)行權(quán)返回, 再?gòu)臅和5牡胤嚼^續(xù)往后執(zhí)行。 它的最大優(yōu)點(diǎn), 就是代碼的寫法非常像同步操作, 如果去除 yield 命令,簡(jiǎn)直一模一樣。

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

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

整個(gè) Generator 函數(shù)就是一個(gè)封裝的異步任務(wù), 或者說(shuō)是異步任務(wù)的容器。 異步操作需要暫停的地方, 都用yield語(yǔ)句注明。 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ù), 會(huì)返回一個(gè)內(nèi)部指針( 即遍歷器) g。 這是 Generator 函數(shù)不同于普通函數(shù)的另一個(gè)地方, 即執(zhí)行它不會(huì)返回結(jié)果, 返回的是指針對(duì)象。 調(diào)用指針 g 的 next 方法, 會(huì)移動(dòng)內(nèi)部指針( 即執(zhí)行異步任務(wù)的第一段), 指向第一個(gè)遇到的 yield 語(yǔ)句, 上例是執(zhí)行到x + 2 為止。

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

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

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

上面代碼中, 第一個(gè) next 方法的 value 屬性, 返回表達(dá)式x + 2 的值( 3)。 第二個(gè) next 方法帶有參數(shù) 2, 這個(gè)參數(shù)可以傳入 Generator 函數(shù), 作為上個(gè)階段異步任務(wù)的返回結(jié)果, 被函數(shù)體內(nèi)的變量 y 接收。 因此, 這一步的 value 屬性, 返回的就是 2( 變量 y 的值)。

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

function* gen(x) {
  try {
    var y = yield x + 2;
  } catch(e) {
    console.log(e);
  }
  return y;
}
var g = gen(1);
g.next();
g.throw(' 出錯(cuò)了 ');

上面代碼的最后一行, Generator 函數(shù)體外, 使用指針對(duì)象的throw 方法拋出的錯(cuò)誤, 可以被函數(shù)體內(nèi)的try...catch 代碼塊捕獲。 這意味著, 出錯(cuò)的代碼與處理錯(cuò)誤的代碼, 實(shí)現(xiàn)了時(shí)間和空間上的分離, 這對(duì)于異步編程無(wú)疑是很重要的。

4. 異步任務(wù)的封裝

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

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ù)封裝了一個(gè)異步操作, 該操作先讀取一個(gè)遠(yuǎn)程接口, 然后從 JSON 格式的數(shù)據(jù)解析信息。 就像前面說(shuō)過(guò)的, 這段代碼非常像同步操作, 除了加上了 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ù), 獲取遍歷器對(duì)象, 然后使用 next 方法( 第二行), 執(zhí)行異步任務(wù)的第一階段。 由于 Fetch 模塊返回的是一個(gè)Promise 對(duì)象, 因此要用 then 方法調(diào)用下一個(gè) next 方法。

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

以上是“ES6中javascript如何實(shí)現(xiàn)異步操作”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI