成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JS 異步編程六種方案

longmon / 3244人閱讀

摘要:接下來介紹下異步編程六種方法。六生成器函數(shù)是提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同,最大的特點就是可以控制函數(shù)的執(zhí)行。參考文章前端面試之道異步編程的種方法你不知道的中卷函數(shù)的含義和用法替代的個理由

前言

我們知道Javascript語言的執(zhí)行環(huán)境是"單線程"。也就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執(zhí)行后面一個任務。

這種模式雖然實現(xiàn)起來比較簡單,執(zhí)行環(huán)境相對單純,但是只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執(zhí)行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript代碼長時間運行(比如死循環(huán)),導致整個頁面卡在這個地方,其他任務無法執(zhí)行。

為了解決這個問題,Javascript語言將任務的執(zhí)行模式分成兩種:同步和異步。本文主要介紹異步編程幾種辦法,并通過比較,得到最佳異步編程的解決方案!

想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客

一、同步與異步

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

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

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

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

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

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

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

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

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

三、事件監(jiān)聽

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

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

f1.on("done", f2);

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

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

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

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

四、發(fā)布訂閱

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

首先,f2向信號中心jQuery訂閱done信號。

jQuery.subscribe("done", f2);

然后,f1進行如下改寫:

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

上面代碼中,jQuery.publish("done")的意思是,f1執(zhí)行完成后,向信號中心jQuery發(fā)布done信號,從而引發(fā)f2的執(zhí)行。
f2完成執(zhí)行后,可以取消訂閱(unsubscribe)

jQuery.unsubscribe("done", f2);

這種方法的性質(zhì)與“事件監(jiān)聽”類似,但是明顯優(yōu)于后者。因為可以通過查看“消息中心”,了解存在多少信號、每個信號有多少訂閱者,從而監(jiān)控程序的運行。

五、Promise/A+

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

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

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

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

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

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

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

當我們在構造 Promise 的時候,構造函數(shù)內(nèi)部的代碼是立即執(zhí)行的

new Promise((resolve, reject) => {
  console.log("new Promise")
  resolve("success")
})
console.log("end")
// new Promise => end
2.promise的鏈式調(diào)用

每次調(diào)用返回的都是一個新的Promise實例(這就是then可用鏈式調(diào)用的原因)

如果then中返回的是一個結果的話會把這個結果傳遞下一次then中的成功回調(diào)

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

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

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

catch 會捕獲到?jīng)]有捕獲的異常

接下來我們看幾個例子:

  // 例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)異常,會走下一個then的失敗回調(diào)
  }) //由于下一個then沒有失敗回調(diào),就會繼續(xù)往下找,如果都沒有,就會被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不僅能夠捕獲錯誤,而且也很好地解決了回調(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))

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

六、生成器Generators/ yield

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

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

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

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

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

我們先來看個例子:

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}

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

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

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

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

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

我們再來看個例子:有三個本地文件,分別1.txt,2.txt和3.txt,內(nèi)容都只有一句話,下一個請求依賴上一個請求的結果,想通過Generator函數(shù)依次調(diào)用三個文件

//1.txt文件
2.txt
//2.txt文件
3.txt
//3.txt文件
結束
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是個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=>結束
    })
  })
})
// 2.txt=>3.txt=>結束

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

安裝co庫只需:npm install co

上面例子只需兩句話就可以輕松實現(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=>結束=>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簡介

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

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

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

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

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

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

Generator函數(shù)依次調(diào)用三個文件那個例子用async/await寫法,只需幾句話便可實現(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后面跟的是一個Promise實例
    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ù)返回的也是個promise
  data => {
    console.log(data)
  },
  err => console.log(err)
)
// p1 2.txt
// p2 3.txt
// p3 結束
// 結束
2.Async/Await并發(fā)請求

如果請求兩個文件,毫無關系,可以通過并發(fā)請求

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()//這個函數(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
八、總結

1.JS 異步編程進化史:callback -> promise -> generator -> async + await

2.async/await 函數(shù)的實現(xiàn),就是將 Generator 函數(shù)和自動執(zhí)行器,包裝在一個函數(shù)里。

3.async/await可以說是異步終極解決方案了。

(1) async/await函數(shù)相對于Promise,優(yōu)勢體現(xiàn)在

處理 then 的調(diào)用鏈,能夠更清晰準確的寫出代碼

并且也能優(yōu)雅地解決回調(diào)地獄問題。

當然async/await函數(shù)也存在一些缺點,因為 await 將異步代碼改造成了同步代碼,如果多個異步代碼沒有依賴性卻使用了 await 會導致性能上的降低,代碼沒有依賴性的話,完全可以使用 Promise.all 的方式。

(2) async/await函數(shù)對 Generator 函數(shù)的改進,體現(xiàn)在以下三點

內(nèi)置執(zhí)行器。

Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,所以才有了 co 函數(shù)庫,而 async 函數(shù)自帶執(zhí)行器。也就是說,async 函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一行。

更廣的適用性。 co 函數(shù)庫約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise 對象,而 async 函數(shù)的 await 命令后面,可以跟 Promise 對象和原始類型的值(數(shù)值、字符串和布爾值,但這時等同于同步操作)。

更好的語義。 async 和 await,比起星號和 yield,語義更清楚了。async 表示函數(shù)里有異步操作,await 表示緊跟在后面的表達式需要等待結果。

參考文章

Promises/A+

前端面試之道

Javascript異步編程的4種方法

你不知道的JavaScript(中卷)

async 函數(shù)的含義和用法

Async/Await替代Promise的6個理由

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/101004.html

相關文章

  • 總結異步編程六種方式

    摘要:以下為幾種異步編程方式的總結,希望與君共勉?;卣{(diào)函數(shù)事件監(jiān)聽發(fā)布訂閱模式異步編程傳統(tǒng)的解決方案回調(diào)函數(shù)和事件監(jiān)聽初始示例假設有兩個函數(shù)和,是一個需要一定時間的函數(shù)。 異步編程 眾所周知 JavaScript 是單線程工作,也就是只有一個腳本執(zhí)行完成后才能執(zhí)行下一個腳本,兩個腳本不能同時執(zhí)行,如果某個腳本耗時很長,后面的腳本都必須排隊等著,會拖延整個程序的執(zhí)行。以下為幾種異步編程方式的總...

    coolpail 評論0 收藏0
  • 雙十二大前端工程師讀書清單

    摘要:本文最早為雙十一而作,原標題雙大前端工程師讀書清單,以付費的形式發(fā)布在上。發(fā)布完本次預告后,捕捉到了一個友善的吐槽讀書清單也要收費。這本書便從的異步編程講起,幫助我們設計快速響應的網(wǎng)絡應用,而非簡單的頁面。 本文最早為雙十一而作,原標題雙 11 大前端工程師讀書清單,以付費的形式發(fā)布在 GitChat 上。發(fā)布之后在讀者圈群聊中和讀者進行了深入的交流,現(xiàn)免費分享到這里,不足之處歡迎指教...

    happen 評論0 收藏0
  • 雙十二大前端工程師讀書清單

    摘要:本文最早為雙十一而作,原標題雙大前端工程師讀書清單,以付費的形式發(fā)布在上。發(fā)布完本次預告后,捕捉到了一個友善的吐槽讀書清單也要收費。這本書便從的異步編程講起,幫助我們設計快速響應的網(wǎng)絡應用,而非簡單的頁面。 本文最早為雙十一而作,原標題雙 11 大前端工程師讀書清單,以付費的形式發(fā)布在 GitChat 上。發(fā)布之后在讀者圈群聊中和讀者進行了深入的交流,現(xiàn)免費分享到這里,不足之處歡迎指教...

    余學文 評論0 收藏0
  • 雙十二大前端工程師讀書清單

    摘要:本文最早為雙十一而作,原標題雙大前端工程師讀書清單,以付費的形式發(fā)布在上。發(fā)布完本次預告后,捕捉到了一個友善的吐槽讀書清單也要收費。這本書便從的異步編程講起,幫助我們設計快速響應的網(wǎng)絡應用,而非簡單的頁面。 本文最早為雙十一而作,原標題雙 11 大前端工程師讀書清單,以付費的形式發(fā)布在 GitChat 上。發(fā)布之后在讀者圈群聊中和讀者進行了深入的交流,現(xiàn)免費分享到這里,不足之處歡迎指教...

    Jochen 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<