溫馨提示×

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

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

JS異步編程方案有哪些

發(fā)布時(shí)間:2021-11-06 16:33:37 來源:億速云 閱讀:90 作者:iii 欄目:web開發(fā)

本篇內(nèi)容主要講解“JS異步編程方案有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“JS異步編程方案有哪些”吧!

一、同步與異步

我們可以通俗理解為異步就是一個(gè)任務(wù)分成兩段,先執(zhí)行***段,然后轉(zhuǎn)而執(zhí)行其他任務(wù),等做好了準(zhǔn)備,再回過頭執(zhí)行第二段。排在異步任務(wù)后面的代碼,不用等待異步任務(wù)結(jié)束會(huì)馬上運(yùn)行,也就是說,異步任務(wù)不具有”堵塞“效應(yīng)。比如,有一個(gè)任務(wù)是讀取文件進(jìn)行處理,異步的執(zhí)行過程就是下面這樣

JS異步編程方案有哪些

這種不連續(xù)的執(zhí)行,就叫做異步。相應(yīng)地,連續(xù)的執(zhí)行,就叫做同步

JS異步編程方案有哪些

"異步模式"非常重要。在瀏覽器端,耗時(shí)很長(zhǎng)的操作都應(yīng)該異步執(zhí)行,避免瀏覽器失去響應(yīng),***的例子就是Ajax操作。在服務(wù)器端,"異步模式"甚至是唯一的模式,因?yàn)閳?zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有http請(qǐng)求,服務(wù)器性能會(huì)急劇下降,很快就會(huì)失去響應(yīng)。接下來介紹下異步編程六種方法。

二、回調(diào)函數(shù)(Callback)

回調(diào)函數(shù)是異步操作最基本的方法。以下代碼就是一個(gè)回調(diào)函數(shù)的例子:

ajax(url, () => {      // 處理邏輯  })

但是回調(diào)函數(shù)有一個(gè)致命的弱點(diǎn),就是容易寫出回調(diào)地獄(Callback hell)。假設(shè)多個(gè)請(qǐng)求存在依賴性,你可能就會(huì)寫出如下代碼:

ajax(url, () => {      // 處理邏輯      ajax(url1, () => {          // 處理邏輯          ajax(url2, () => {              // 處理邏輯          })      })  })

回調(diào)函數(shù)的優(yōu)點(diǎn)是簡(jiǎn)單、容易理解和實(shí)現(xiàn),缺點(diǎn)是不利于代碼的閱讀和維護(hù),各個(gè)部分之間高度耦合,使得程序結(jié)構(gòu)混亂、流程難以追蹤(尤其是多個(gè)回調(diào)函數(shù)嵌套的情況),而且每個(gè)任務(wù)只能指定一個(gè)回調(diào)函數(shù)。此外它不能使用 try catch 捕獲錯(cuò)誤,不能直接 return。

三、事件監(jiān)聽

這種方式下,異步任務(wù)的執(zhí)行不取決于代碼的順序,而取決于某個(gè)事件是否發(fā)生。

下面是兩個(gè)函數(shù)f1和f2,編程的意圖是f2必須等到f1執(zhí)行完成,才能執(zhí)行。首先,為f1綁定一個(gè)事件(這里采用的jQuery的寫法)

f1.on('done', f2);

上面這行代碼的意思是,當(dāng)f1發(fā)生done事件,就執(zhí)行f2。然后,對(duì)f1進(jìn)行改寫:

function f1() {    setTimeout(function () {      // ...      f1.trigger('done');    }, 1000);  }

上面代碼中,f1.trigger('done')表示,執(zhí)行完成后,立即觸發(fā)done事件,從而開始執(zhí)行f2。

這種方法的優(yōu)點(diǎn)是比較容易理解,可以綁定多個(gè)事件,每個(gè)事件可以指定多個(gè)回調(diào)函數(shù),而且可以"去耦合",有利于實(shí)現(xiàn)模塊化。缺點(diǎn)是整個(gè)程序都要變成事件驅(qū)動(dòng)型,運(yùn)行流程會(huì)變得很不清晰。閱讀代碼的時(shí)候,很難看出主流程。

四、發(fā)布訂閱

我們假定,存在一個(gè)"信號(hào)中心",某個(gè)任務(wù)執(zhí)行完成,就向信號(hào)中心"發(fā)布"(publish)一個(gè)信號(hào),其他任務(wù)可以向信號(hào)中心"訂閱"(subscribe)這個(gè)信號(hào),從而知道什么時(shí)候自己可以開始執(zhí)行。這就叫做"發(fā)布/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。

首先,f2向信號(hào)中心jQuery訂閱done信號(hào)。

jQuery.subscribe('done', f2);

然后,f1進(jìn)行如下改寫:

function f1() {    setTimeout(function () {      // ...      jQuery.publish('done');    }, 1000);  }

上面代碼中,jQuery.publish('done')的意思是,f1執(zhí)行完成后,向信號(hào)中心jQuery發(fā)布done信號(hào),從而引發(fā)f2的執(zhí)行。

f2完成執(zhí)行后,可以取消訂閱(unsubscribe)

jQuery.unsubscribe('done', f2);

這種方法的性質(zhì)與“事件監(jiān)聽”類似,但是明顯優(yōu)于后者。因?yàn)榭梢酝ㄟ^查看“消息中心”,了解存在多少信號(hào)、每個(gè)信號(hào)有多少訂閱者,從而監(jiān)控程序的運(yùn)行。

五、Promise/A+

Promise本意是承諾,在程序中的意思就是承諾我過一段時(shí)間后會(huì)給你一個(gè)結(jié)果。 什么時(shí)候會(huì)用到過一段時(shí)間?答案是異步操作,異步是指可能比較長(zhǎng)時(shí)間才有結(jié)果的才做,例如網(wǎng)絡(luò)請(qǐng)求、讀取本地文件等

1.Promise的三種狀態(tài)

  •  Pending----Promise對(duì)象實(shí)例創(chuàng)建時(shí)候的初始狀態(tài)

  •  Fulfilled----可以理解為成功的狀態(tài)

  •  Rejected----可以理解為失敗的狀態(tài)

JS異步編程方案有哪些

這個(gè)承諾一旦從等待狀態(tài)變成為其他狀態(tài)就永遠(yuǎn)不能更改狀態(tài)了,比如說一旦狀態(tài)變?yōu)?resolved 后,就不能再次改變?yōu)镕ulfilled

let p = new Promise((resolve, reject) => {    reject('reject')    resolve('success')//無效代碼不會(huì)執(zhí)行  })  p.then(    value => {      console.log(value)    },    reason => {      console.log(reason)//reject    }  )

當(dāng)我們?cè)跇?gòu)造 Promise 的時(shí)候,構(gòu)造函數(shù)內(nèi)部的代碼是立即執(zhí)行的

new Promise((resolve, reject) => {    console.log('new Promise')    resolve('success')  })  console.log('end')  // new Promise => end

2.promise的鏈?zhǔn)秸{(diào)用

  •  每次調(diào)用返回的都是一個(gè)新的Promise實(shí)例(這就是then可用鏈?zhǔn)秸{(diào)用的原因)

  •  如果then中返回的是一個(gè)結(jié)果的話會(huì)把這個(gè)結(jié)果傳遞下一次then中的成功回調(diào)

  •  如果then中出現(xiàn)異常,會(huì)走下一個(gè)then的失敗回調(diào)

  •  在 then中使用了return,那么 return 的值會(huì)被Promise.resolve() 包裝(見例1,2)

  •  then中可以不傳遞參數(shù),如果不傳遞會(huì)透到下一個(gè)then中(見例3)

  •  catch 會(huì)捕獲到?jīng)]有捕獲的異常

接下來我們看幾個(gè)例子:

// 例1  Promise.resolve(1)  .then(res => {    console.log(res)    return 2 //包裝成 Promise.resolve(2)  })  .catch(err => 3)  .then(res => console.log(res))
// 例2  Promise.resolve(1)    .then(x => x + 1)    .then(x => {      throw new Error('My Error')    })    .catch(() => 1)    .then(x => x + 1)    .then(x => console.log(x)) //2    .catch(console.error)
// 例3  let fs = require('fs')  function read(url) {    return new Promise((resolve, reject) => {      fs.readFile(url, 'utf8', (err, data) => {        if (err) reject(err)        resolve(data)      })    })  }  read('./name.txt')    .then(function(data) {      throw new Error() //then中出現(xiàn)異常,會(huì)走下一個(gè)then的失敗回調(diào)    }) //由于下一個(gè)then沒有失敗回調(diào),就會(huì)繼續(xù)往下找,如果都沒有,就會(huì)被catch捕獲到    .then(function(data) {      console.log('data')    })    .then()    .then(null, function(err) {      console.log('then', err)// then error    })    .catch(function(err) {      console.log('error')    })

Promise不僅能夠捕獲錯(cuò)誤,而且也很好地解決了回調(diào)地獄的問題,可以把之前的回調(diào)地獄例子改寫為如下代碼:

ajax(url)    .then(res => {        console.log(res)        return ajax(url1)    }).then(res => {        console.log(res)        return ajax(url2)    }).then(res => console.log(res))

它也是存在一些缺點(diǎn)的,比如無法取消 Promise,錯(cuò)誤需要通過回調(diào)函數(shù)捕獲。

六、生成器Generators/ yield

Generator 函數(shù)是 ES6 提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同,Generator ***的特點(diǎn)就是可以控制函數(shù)的執(zhí)行。

  •  語法上,首先可以把它理解成,Generator 函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)。

  •  Generator 函數(shù)除了狀態(tài)機(jī),還是一個(gè)遍歷器對(duì)象生成函數(shù)。

  •  可暫停函數(shù), yield可暫停,next方法可啟動(dòng),每次返回的是yield后的表達(dá)式結(jié)果。

  •  yield表達(dá)式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個(gè)參數(shù),該參數(shù)就會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值。

我們先來看個(gè)例子:

function *foo(x) {    let y = 2 * (yield (x + 1))    let z = yield (y / 3)    return (x + y + z)  }  let it = foo(5)  console.log(it.next())   // => {value: 6, done: false}  console.log(it.next(12)) // => {value: 8, done: false}  console.log(it.next(13)) // => {value: 42, done: true}

可能結(jié)果跟你想象不一致,接下來我們逐行代碼分析:

  •  首先 Generator 函數(shù)調(diào)用和普通函數(shù)不同,它會(huì)返回一個(gè)迭代器

  •  當(dāng)執(zhí)行***次 next 時(shí),傳參會(huì)被忽略,并且函數(shù)暫停在 yield (x + 1) 處,所以返回 5 + 1 = 6

  •  當(dāng)執(zhí)行第二次 next 時(shí),傳入的參數(shù)12就會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值,如果你不傳參,yield 永遠(yuǎn)返回 undefined。此時(shí) let y = 2 12,所以第二個(gè) yield 等于 2 12 / 3 = 8

  •  當(dāng)執(zhí)行第三次 next 時(shí),傳入的參數(shù)13就會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值,所以 z = 13, x = 5, y = 24,相加等于 42

我們?cè)賮砜磦€(gè)例子:有三個(gè)本地文件,分別1.txt,2.txt和3.txt,內(nèi)容都只有一句話,下一個(gè)請(qǐng)求依賴上一個(gè)請(qǐng)求的結(jié)果,想通過Generator函數(shù)依次調(diào)用三個(gè)文件

//1.txt文件  2.txt
//2.txt文件  3.txt
//3.txt文件  結(jié)束
let fs = require('fs')  function read(file) {    return new Promise(function(resolve, reject) {      fs.readFile(file, 'utf8', function(err, data) {        if (err) reject(err)        resolve(data)      })    })  }  function* r() {    let r1 = yield read('./1.txt')    let r2 = yield read(r1)    let r3 = yield read(r2)    console.log(r1)    console.log(r2)    console.log(r3)  }  let it = r()  let { value, done } = it.next()  value.then(function(data) { // value是個(gè)promise    console.log(data) //data=>2.txt    let { value, done } = it.next(data)    value.then(function(data) {      console.log(data) //data=>3.txt      let { value, done } = it.next(data)      value.then(function(data) {        console.log(data) //data=>結(jié)束      })    })  })  // 2.txt=>3.txt=>結(jié)束

從上例中我們看出手動(dòng)迭代Generator 函數(shù)很麻煩,實(shí)現(xiàn)邏輯有點(diǎn)繞,而實(shí)際開發(fā)一般會(huì)配合 co 庫去使用。co是一個(gè)為Node.js和瀏覽器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加優(yōu)雅的方式編寫非阻塞代碼。

安裝co庫只需:npm install co

上面例子只需兩句話就可以輕松實(shí)現(xiàn)

function* r() {    let r1 = yield read('./1.txt')    let r2 = yield read(r1)    let r3 = yield read(r2)    console.log(r1)    console.log(r2)    console.log(r3)  }  let co = require('co')  co(r()).then(function(data) {    console.log(data)  })  // 2.txt=>3.txt=>結(jié)束=>undefined

我們可以通過 Generator 函數(shù)解決回調(diào)地獄的問題,可以把之前的回調(diào)地獄例子改寫為如下代碼:

function *fetch() {      yield ajax(url, () => {})      yield ajax(url1, () => {})      yield ajax(url2, () => {})  }  let it = fetch()  let result1 = it.next()  let result2 = it.next()  let result3 = it.next()

七、async/await

1.Async/Await簡(jiǎn)介

使用async/await,你可以輕松地達(dá)成之前使用生成器和co函數(shù)所做到的工作,它有如下特點(diǎn):

  •  async/await是基于Promise實(shí)現(xiàn)的,它不能用于普通的回調(diào)函數(shù)。

  •  async/await與Promise一樣,是非阻塞的。

  •  async/await使得異步代碼看起來像同步代碼,這正是它的魔力所在。

一個(gè)函數(shù)如果加上 async ,那么該函數(shù)就會(huì)返回一個(gè) Promise

async function async1() {    return "1"  }  console.log(async1()) // -> Promise {<resolved>: "1"}

Generator函數(shù)依次調(diào)用三個(gè)文件那個(gè)例子用async/await寫法,只需幾句話便可實(shí)現(xiàn)

let fs = require('fs')  function read(file) {    return new Promise(function(resolve, reject) {      fs.readFile(file, 'utf8', function(err, data) {        if (err) reject(err)        resolve(data)      })    })  }  async function readResult(params) {    try {      let p1 = await read(params, 'utf8')//await后面跟的是一個(gè)Promise實(shí)例      let p2 = await read(p1, 'utf8')      let p3 = await read(p2, 'utf8')      console.log('p1', p1)      console.log('p2', p2)      console.log('p3', p3)      return p3    } catch (error) {      console.log(error)    }  }  readResult('1.txt').then( // async函數(shù)返回的也是個(gè)promise    data => {      console.log(data)    },    err => console.log(err)  )  // p1 2.txt  // p2 3.txt  // p3 結(jié)束  // 結(jié)束

2.Async/Await并發(fā)請(qǐng)求

如果請(qǐng)求兩個(gè)文件,毫無關(guān)系,可以通過并發(fā)請(qǐng)求

let fs = require('fs')  function read(file) {    return new Promise(function(resolve, reject) {      fs.readFile(file, 'utf8', function(err, data) {        if (err) reject(err)        resolve(data)      })    })  }  function readAll() {    read1()    read2()//這個(gè)函數(shù)同步執(zhí)行  }  async function read1() {    let r = await read('1.txt','utf8')    console.log(r)  }  async function read2() {    let r = await read('2.txt','utf8')    console.log(r)  }  readAll() // 2.txt 3.txt

到此,相信大家對(duì)“JS異步編程方案有哪些”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(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)容。

js
AI