ES6核心內容精講--快速實踐ES6(三)

来源:http://www.cnblogs.com/ang-/archive/2017/05/26/6910276.html
-Advertisement-
Play Games

Promise 是什麼 Promise是非同步編程的一種解決方案。Promise對象表示了非同步操作的最終狀態(完成或失敗)和返回的結果。 其實我們在jQuery的ajax中已經見識了部分Promise的實現,通過Promise,我們能夠將回調轉換為鏈式調用,也起到解耦的作用。 怎麼用 Promise接 ...


Promise

是什麼

Promise是非同步編程的一種解決方案。Promise對象表示了非同步操作的最終狀態(完成或失敗)和返回的結果。

其實我們在jQuery的ajax中已經見識了部分Promise的實現,通過Promise,我們能夠將回調轉換為鏈式調用,也起到解耦的作用。

怎麼用

Promise介面的基本思想是讓非同步操作返回一個Promise對象

三種狀態和兩種變化途徑

Promise對象只有三種狀態。

  • 非同步操作“未完成”(pending)
  • 非同步操作“已完成”(resolved,又稱fulfilled)
  • 非同步操作“失敗”(rejected)

這三種的狀態的變化途徑只有兩種。

  • 非同步操作從“未完成”到“已完成”
  • 非同步操作從“未完成”到“失敗”。

這種變化只能發生一次,一旦當前狀態變為“已完成”或“失敗”,就意味著不會再有新的狀態變化了。因此,Promise對象的最終結果只有兩種。

非同步操作成功,Promise對象傳回一個值,狀態變為resolved。

非同步操作失敗,Promise對象拋出一個錯誤,狀態變為rejected。

生成Promise對象

通過new Promise來生成Promise對象:

var promise = new Promise(function(resolve, reject) {
  // 非同步操作的代碼

  if (/* 非同步操作成功 */){
    resolve(value)
  } else {
    reject(error)
  }
})

Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供,不用自己部署。

resolve會將Promise對象的狀態從pending變為resolved,reject則是將Promise對象的狀態從pending變為rejected。

Promise構造函數接受一個函數後會立即執行這個函數

var promise = new Promise(function () {
    console.log('Hello World')
})
// Hello World

then和catch回調

Promise對象生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為resolved時調用,第二個回調函數是Promise對象的狀態變為rejected時調用。第二個函數是可選的。分別稱之為成功回調和失敗回調。成功回調接收非同步操作成功的結果為參數,失敗回調接收非同步操作失敗報出的錯誤作為參數。

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('成功')
    }, 3000)
})

promise.then(function (data){
    console.log(data)
})
// 3s後列印'成功'

catch方法是then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject('失敗')
    }, 3000)
})

promise.catch(function (data){
    console.log(data)
})
// 3s後列印'失敗'

Promise.all()

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

var p = Promise.all([p1, p2, p3])

上面代碼中,Promise.all方法接受一個數組作為參數,p1、p2、p3都是Promise對象的實例,如果不是,就會先調用下麵講到的Promise.resolve方法,將參數轉為Promise實例,再進一步處理。(Promise.all方法的參數可以不是數組,但必須具有Iterator介面,且返回的每個成員都是Promise實例。)

p的狀態由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態都變成resolved,p的狀態才會變成resolved,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1、p2、p3之中有一個被Rejected,p的狀態就變成Rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

Promise.race()

與Promise.all()類似,不過是只要有一個Promise實例先改變了狀態,p的狀態就是它的狀態,傳遞給回調函數的結果也是它的結果。所以很形象地叫做賽跑。

Promise.resolve()和Promise.reject()

有時需要將現有對象轉為Promise對象,可以使用這兩個方法。

Generator(生成器)

是什麼

生成器本質上是一種特殊的迭代器(參見本文章系列二之Iterator)。ES6里的迭代器並不是一種新的語法或者是新的內置對象(構造函數),而是一種協議 (protocol)。所有遵循了這個協議的對象都可以稱之為迭代器對象。生成器對象由生成器函數返回並且遵守了迭代器協議。具體參見MDN。

怎麼用

執行過程

生成器函數的語法為在function,在其函數體內部可以使用yield和yield關鍵字。

function* gen(x){
  console.log(1)
  var y = yield x + 2
  console.log(2)
  return y
}

var g = gen(1)

當我們像上面那樣調用生成器函數時,會發現並沒有輸出。這就是生成器函數與普通函數的不同,它可以交出函數的執行權(即暫停執行)。yield表達式就是暫停標誌。

之前提到了生成器對象遵循迭代器協議,所以其實可以通過next方法執行。執行結果也是一個包含value和done屬性的對象。

遍歷器對象的next方法的運行邏輯如下。

(1)遇到yield表達式,就暫停執行後面的操作,並將緊跟在yield後面的那個表達式的值,作為返回的對象的value屬性值。

(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。

(3)如果沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句為止,並將return語句後面的表達式的值,作為返回的對象的value屬性值。

(4)如果該函數沒有return語句,則返回的對象的value屬性值為undefined。

需要註意的是,yield表達式後面的表達式,只有當調用next方法、內部指針指向該語句時才會執行。

g.next() 
// 1
// { value: 3, done: false }
g.next() 
// 2
// { value: undefined, done: true }

for...of遍歷

生成器部署了迭代器介面,因此可以用for...of來遍歷,不用調用next方法

function *foo() {
  yield 1
  yield 2
  yield 3
  return 4
}

for (let v of foo()) {
  console.log(v)
}

// 1
// 2
// 3

yield*表達式

從語法角度看,如果yield表達式後面跟的是一個遍歷器對象,需要在yield表達式後面加上星號,表明它返回的是一個遍歷器對象。這被稱為yield表達式。yield後面只能跟迭代器,yield*的功能是將迭代控制權交給後面的迭代器,達到遞歸迭代的目的

function* foo() {
  yield 'a'
  yield 'b'
}

function* bar() {
  yield 'x'
  yield* foo()
  yield 'y'
}

for (let v of bar()) {
  console.log(v)
}

// x
// a
// b
// y

自動執行

下麵是使用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函數將非同步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段)。

那麼如何自動化非同步任務的流程管理呢?

Generator函數就是一個非同步操作的容器。它的自動執行需要一種機制,當非同步操作有了結果,能夠自動交回執行權。

兩種方法可以做到這一點。

  1. 回調函數。將非同步操作包裝成Thunk函數,在回調函數裡面交回執行權。

  2. Promise對象。將非同步操作包裝成Promise對象,用then方法交回執行權。

Thunk函數

本節很簡略,可能會看不太明白,請參考Thunk 函數的含義和用法

Thunk函數的含義:編譯器的"傳名調用"實現,往往是將參數放到一個臨時函數之中,再將這個臨時函數傳入函數體。這個臨時函數就叫做Thunk函數。

JavaScript語言是傳值調用,它的Thunk函數含義有所不同。在JavaScript語言中,Thunk函數替換的不是表達式,而是多參數函數,將其替換成單參數的版本,且只接受回調函數作為參數。

任何函數,只要參數有回調函數,就能寫成Thunk函數的形式,可以通過一個Thunk函數轉換器來轉換。

Thunk函數真正的威力,在於可以自動執行Generator函數。我們可以實現一個基於Thunk函數的Generator執行器,然後直接把Generator函數傳入這個執行器即可。

function run(fn) {
  var gen = fn()

  function next(err, data) {
    var result = gen.next(data)
    if (result.done) return
    result.value(next)
  }

  next()
}

function* g() {
  // ...
}

run(g)

Thunk函數並不是Generator函數自動執行的唯一方案。因為自動執行的關鍵是,必須有一種機制,自動控制Generator函數的流程,接收和交還程式的執行權。回調函數可以做到這一點,Promise對象也可以做到這一點。

基於Promise對象的自動執行

首先,將方法包裝成一個Promise對象(fs是nodejs的一個內置模塊)。

var fs = require('fs')

var readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function (error, data) {
      if (error) reject(error)
      resolve(data)
    })
  })
}

var gen = function* () {
  var f1 = yield readFile('/etc/fstab')
  var f2 = yield readFile('/etc/shells')
  console.log(f1.toString())
  console.log(f2.toString())
}

然後,手動執行上面的Generator函數。

var g = gen()

g.next().value.then(function (data) {
  g.next(data).value.then(function (data) {
    g.next(data)
  })
})

觀察上面的執行過程,其實是在遞歸調用,我們可以用一個函數來實現:

function run(gen){
  var g = gen()

  function next(data){
    var result = g.next(data)
    if (result.done) return result.value
    result.value.then(function(data){
      next(data)
    })
  }

  next()
}

run(gen)

上面代碼中,只要Generator函數還沒執行到最後一步,next函數就調用自身,以此實現自動執行。

co模塊

co模塊是nodejs社區著名的TJ大神寫的一個小工具,用於Generator函數的自動執行。

下麵是一個Generator函數,用於依次讀取兩個文件

var gen = function* () {
  var f1 = yield readFile('/etc/fstab')
  var f2 = yield readFile('/etc/shells')
  console.log(f1.toString())
  console.log(f2.toString())
}

var co = require('co')
co(gen)

co模塊可以讓你不用編寫Generator函數的執行器。Generator函數只要傳入co函數,就會自動執行。co函數返回一個Promise對象,因此可以用then方法添加回調函數。

co(gen).then(function () {
  console.log('Generator 函數執行完成')
})

co模塊的原理:其實就是將兩種自動執行器(Thunk函數和Promise對象),包裝成一個模塊。使用co的前提條件是,Generator函數的yield命令後面,只能是Thunk函數或Promise對象。如果數組或對象的成員,全部都是Promise對象,也可以使用co(co v4.0版以後,yield命令後面只能是Promise對象,不再支持Thunk函數)。

async(非同步)函數

是什麼

async函數屬於ES7。目前,它仍處於提案階段,但是轉碼器Babel和regenerator都已經支持。async函數可以說是目前非同步操作最好的解決方案,是對Generator函數的升級和改進。

怎麼用

1)語法

async函數聲明定義了非同步函數,它會返回一個AsyncFunction對象。和普通函數一樣,你也可以定義一個非同步函數表達式。

調用非同步函數時會返回一個promise對象。當這個非同步函數成功返回一個值時,將會使用promise的resolve方法來處理這個返回值,當非同步函數拋出的是異常或者非法值時,將會使用promise的reject方法來處理這個異常值。

非同步函數可能會包括await表達式,這將會使非同步函數暫停執行並等待promise解析傳值後,繼續執行非同步函數並返回解析值。

註意:await只能用在async函數中。

前面依次讀取兩個文件的代碼寫成async函數如下:

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab')
  var f2 = await readFile('/etc/shells')
  console.log(f1.toString())
  console.log(f2.toString())
}

async函數將Generator函數的星號(*)替換成了async,將yield改為了await。

2)async函數的改進

async函數對Generator函數的改進,體現在以下三點。

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

var result = asyncReadFile()

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

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

3)基本用法

同Generator函數一樣,async函數返回一個Promise對象,可以使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到觸發的非同步操作完成,再接著執行函數體內後面的語句。

function resolveAfter2Seconds (x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x)
    }, 2000)
  })
}

async function add1 (x) {
  var a = resolveAfter2Seconds(20)
  var b = resolveAfter2Seconds(30)
  return x + await a + await b
}

add1(10).then(v => {
  console.log(v)  
})
// 2s後列印60

async function add2 (x) {
  var a = await resolveAfter2Seconds(20)
  var b = await resolveAfter2Seconds(30)
  return x + a + b
}

add2(10).then(v => {
  console.log(v)
})
// 4s後列印60

4)捕獲錯誤

可以使用.catch回調捕獲錯誤,也可以使用傳統的try...catch。

async function myFunction () {
  try {
    await somethingThatReturnsAPromise()
  } catch (err) {
    console.log(err)
  }
}

// 另一種寫法
async function myFunction () {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err)
  }
}

5)併發的非同步操作

let foo = await getFoo()
let bar = await getBar()

多個await命令後面的非同步操作會按順序完成。如果不存在繼發關係,最好讓它們同時觸發。上面的代碼只有getFoo完成,才會去執行getBar,這樣會比較耗時。如果這兩個是獨立的非同步操作,完全可以讓它們同時觸發。

// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()])

// 寫法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 也許很多朋友在學習NIO的時候都會感覺有點吃力,對裡面的很多概念都感覺不是那麼明朗。在進入Java NIO編程之前,我們今天先來討論一些比較基礎的知識:I/O模型。下麵本文先從同步和非同步的概念 說起,然後接著闡述了阻塞和非阻塞的區別,接著介紹了阻塞IO和非阻塞IO的區別,然後介紹了同步IO和非同步IO... ...
  • 在實際的應用場景中,兩個實體之間不只是簡單的一對一關係,還會出現多對多關係,同時還有可能會出現多對多關係還附帶有其他欄位的情況。本文通過幾個例子,對錶間多對多關係的實體類代碼表示方法進行描述。這種設計方法,並不只是在使用 ORM 框架時需要,事實上,它是 POCO 及簡單 Java 類(POJO)的... ...
  • 圓角邊框以及陰影製作卡片式圖片 圓角邊框 border-radius 卡片使用陰影 box-shadow 利用陰影給圖片底部創造一個長方形 內部的元素會直接覆蓋整個陰影 HTML 部分 CSS 部分 圖片展示 此文到此結束 此文參考鏈接 http://www.w3school.com.cn/cssr ...
  • Base64 is a group of similar binary-to-text encoding schemes that represent binary data in an ASCII string format by translating it into a radix-64 re ...
  • 前文提要:【js實例】js中的5種基本數據類型和9種操作符 Array類型的9個數組方法 Array中有9個數組方法: 1.檢測數組 2.轉換方法 3.棧方法 4.隊列方法 5.沖排序方法6.操作方法 7.位置方法 8.迭代方法 9.歸併方法 在實例中介紹,實例如下 Date類型的41個日期方法 D ...
  • 做為css佈局的又一種新方式,Flex擁有極強的使用效果,相比原來的float,position對元素樣式的操作更加簡潔,本文是我的一點學習經驗和心得吧,如有錯誤以及不足之處,請多多指點。 好進入正題吧,首先借用阮一峰大神的一副圖片: 再來說說flex的幾個屬性吧: 一、容器屬性 1.flex-di ...
  • 1.Generator函數其實是一個封裝了多個內部狀態的狀態機,執行它會返回一個遍歷器對象,然後可以依次遍歷Generator中的每一個狀態,也就是分段執行,yield是暫停執行的標記,next恢復執行。 2.yield: - 一個函數裡面,return只能執行一遍,yield可以執行多次; - G ...
  • JavaScript中的運算符分為以下幾類: 算術運算符 比較運算符 邏輯運算符 賦值運算符 1. 算術運算符 算術運算符用於執行變數與/或值之間的算術運算。 給定 y=5,下麵的表格解釋了這些算術運算符: 註意點: a. 自增和自減 遞增和遞減運算符可以放在變數前也可以放在變數後: 當放在變數前, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...