Generator和Async/Await

引言

接觸過Ajax請求的會遇到過異步調用的問題,爲了保證調用順序的正確性,通常咱們會在回調函數中調用,也有用到一些新的解決方案如Promise相關的技術。html

在異步編程中,還有一種經常使用的解決方案,它就是Generator生成器函數。顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態,生成器返回一個迭代器Iterator對象,咱們能夠經過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。編程

Iterator接口

什麼是Iterator接口promise

遍歷器(Iterator)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。數據結構

Iterator的做用
  1. 爲各類數據結構,提供一個統一的、簡便的訪問接口
  2. 使得數據結構的成員可以按某種次序排列
  3. ES6 創造了一種新的遍歷命令for...of循環,Iterator接口主要供for...of消費。
Iterator實現
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
原生具有Iterator接口的數據結構
  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. 函數的 arguments 對象
  7. NodeList 對象

查看一下Map下面的所掛載的Iteratordom

let map = new Map();
console.log(map.__proto__);

輸出結果:異步

clear:ƒ clear()
constructor:ƒ Map()
delete:ƒ delete()
entries:ƒ entries()
forEach:ƒ forEach()
get:ƒ ()
has:ƒ has()
keys:ƒ keys()
set:ƒ ()
size:(...)
values:ƒ values()
Symbol(Symbol.iterator):ƒ entries()
Symbol(Symbol.toStringTag):"Map"
get size:ƒ size()
__proto__:Object
如何爲Object部署一個Iterator接口
function iteratorObject(obj){
    let keys = Object.keys(obj);
    let index = -1;
    return {
        next(){
            index++;
            return index<keys.length?{
                value:obj[keys[index]],
                key:keys[index],
                done:false
            }:{
                value:undefined,
                key:undefined,
                done:true
            }
        }
    }
}
let obj =  {a:1,b:2,c:3};
let iter = iteratorObject(obj);
console.log(iter.next());
// {value: 1, key: "a", done: false}
console.log(iter.next());
// {value: 2, key: "b", done: false}
console.log(iter.next());
// {value: 3, key: "c", done: false}
console.log(iter.next());
// {value: undefined, key: undefined, done: true}

經過上面的方法能夠簡單的爲Object部署了一個Iterator接口。async

Generator函數

Generator是ES6的新特性,經過yield關鍵字,可讓函數的執行流掛起,那麼便爲改變執行流程提供了可能。異步編程

Generator語法

dome:函數

function * greneratorDome(){
    yield "Hello";
    yield "World";
    return "ending";
}
let grenDome = greneratorDome();
console.log(grenDome)

上面的代碼中定義了一個Generator函數,獲取到了函數返回的對象。下面是其輸出結果。prototype

原型鏈:

greneratorDome {<suspended>}
__proto__:Generator
    __proto__:Generator
    constructor:GeneratorFunction {prototype: Generator, constructor: ƒ, Symbol(Symbol.toStringTag): "GeneratorFunction"}
    next:ƒ next()
    return:ƒ return()
    throw:ƒ throw()
    Symbol(Symbol.toStringTag):"Generator"
    __proto__:Object
[[GeneratorStatus]]:"suspended"
[[GeneratorFunction]]:ƒ* greneratorDome()
[[GeneratorReceiver]]:Window
[[GeneratorLocation]]:test.html:43
[[Scopes]]:Scopes[3]

經過上面的輸出結果能夠看的出來,沿着原型鏈向上查找就存在一個next方法,這個方法與Iterator接口返回的結果是大同小異的。

繼續延續dome代碼,並使用next方法向下執行。

function * greneratorDome(){
    yield "Hello";
    yield "World";
    return "Ending";
}
let grenDome = greneratorDome();
console.log(grenDome.next());
// {value: "Hello", done: false}
console.log(grenDome.next());
// {value: "World", done: false}
console.log(grenDome.next());
// {value: "Ending", done: true}
console.log(grenDome.next());
// {value: undefined, done: true}

在最開始的地方有提到過Generator函數,最後返回的是一個Iterator對象,這也就不難理解了。

異步的Generator

dome

function a (){
    setTimeout(() => {
        alert("我是後彈出");
    },1000)
}
function b (){
    alsert("我是先彈出");
}
function * grenDome (){
    yield a();
    yield b();
}
let gren = grenDome();
gren.next();
gren.next();
// 輸出結果
// 我是先彈出
// 我是後彈出

結合Promise

function a (){
    return new Promise((resolve,reject) => {
        setTimeOut(() => {
            console.log(1)
            resolve("a");
        })
    })
}
function b (){
    return new Promise((resolve,reject) => {
         console.log(2)
        resolve("b");
    })
}
function * grenDome (){
    yield a();
    yield b();
    return new Promise((resolve,reject) => {
        resolve("grenDome內部")
    })
}
let gren = grenDome();

// console.log(gren.next())
// {value: Promise, done: false}
// console.log(gren.next())
// {value: Promise, done: false}
// console.log(gren.next())
// {value: Promise, done: true}
// console.log(gren.next())
// {value: undefined, done: true}

gren.next().value.then((res) => {
    console.log(res);
    // a函數
})
gren.next().value.then((res) => {
    console.log(res);
    // b函數
})
gren.next().value.then((res) => {
    console.log(res);
    // grenDome內部
})
// 輸出結果
// a
// b
// grenDome內部

在上面的代碼中有一點是須要注意的,在grenDome函數裏面最後return出去了一個Promise,可是在輸出的時候雖然done屬性已經爲true可是value裏面仍然會存有一個promise對象,實際上done表示的是對應yield關鍵字的函數已經遍歷完成了。

Async/Await

Async/awaitJavascript編寫異步程序的新方法。以往的異步方法無外乎回調函數和Promise。可是Async/await創建於Promise之上,換句話來講使用了Generator函數作了語法糖。

async函數就是隧道盡頭的亮光,不少人認爲它是異步操做的終極解決方案。

什麼是Async/Await

async顧名思義是「異步」的意思,async用於聲明一個函數是異步的。而await從字面意思上是「等待」的意思,就是用於等待異步完成。而且await只能在async函數中使用。

Async/Await語法
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};
async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
};
asyncPrint('hello world',2000);
// 在2000ms以後輸出`hello world`
返回Promse對象

一般asyncawait都是跟隨Promise一塊兒使用的。爲何這麼說呢?由於async返回的都是一個Promise對象同時async適用於任何類型的函數上。這樣await獲得的就是一個Promise對象,若是不是Promise對象的話那async返回的是什麼就是什麼。

async function f() {
  return 'hello world';
}
f().then(v => console.log(v));
// hello world

async函數返回一個Promise對象,可使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。

function a(){
    return new Promise((resolve,reject) => {
        console.log("a函數")
        resolve("a函數")
    })
}
function b (){
    return new Promise((resolve,reject) => {
        console.log("b函數")
        resolve("b函數")
    })
}
async function dome (){
    let A = await a();
    let B = await b();
    return Promise.resolve([A,B]);
}
dome().then((res) => {
    console.log(res);
});
執行機制

前面已經說過await是等待的意思,以後等前面的代碼執行完成以後纔會繼續向下執行。

function a(){
    return new Promise((resolve,reject) => {
        resolve("a");
        console.log("a:不行")
    })
}
function b (){
    return new Promise((resolve,reject) => {
        resolve("b");
        console.log("b:不行");
    })
}
async function dome (){
    await a();
    await b();
    console.log("雖然我在後面,可是我想要先執行能夠麼?")
}
dome();
// 輸出結果
// a:不行
// b:不行
// 雖然我在後面,可是我想要先執行能夠麼?

另一個列子

function timeout1(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
        console.log("timeout1")
        resolve();
    },ms);
  });
};
function timeout2(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
        console.log("timeout2");
        resolve();
    },ms);
  });
};
async function asyncPrint() {
  await timeout1(1000);
  await timeout2(2000);
};
asyncPrint().then((res) => {
    console.log(res);
}).catch((err) => {
    console.log(err)
})
// 1s 後輸出timeout1
// 3s 後輸出timeout2
// undefined
async、await錯誤處理

JavaScript異步請求確定會有請求失敗的狀況,上面也說到了async返回的是一個Promise對象。既然是返回一個Promise對象的話那處理當異步請求發生錯誤的時候咱們就要處理reject的狀態了。

在Promise中當請求reject的時候咱們可使用catch。爲了保持代碼的健壯性使用async、await的時候咱們使用try catch來處理錯誤。

async function f() {
  await Promise.reject('出錯了');
  await Promise.resolve('hello world');
}

async function b() {
    try {
      await f();
    } catch(err) {
     console.log(err);
    }
}
b();
//  出錯了

總結

Iterator接口

遍歷器對象除了具備next方法,還能夠具備return方法和throw方法。若是你本身寫遍歷器對象生成函數,那麼next方法是必須部署的,return方法和throw方法是否部署是可選的。

Es6提供不少API都是基於Iterator接口,好比解構,for...of循環,拓展運算等。

Generator函數

調用Generator函數,返回一個遍歷器對象,表明Generator函數的內部指針。之後每次調用遍歷器對象的next方法,就會返回一個有着valuedone兩個屬性的對象。
value屬性表示當前的內部狀態的值,是yield語句後面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束

Async/Await

Async/await是近些年來JavaScript最具革命性的新特性之一。他讓讀者意識到使用Promise存在的一些問題,並提供了自身來代替Promise的方案。他使得異步代碼變的再也不明顯,咱們好不容易已經學會並習慣了使用回調函數或者then來處理異步。

相關文章
相關標籤/搜索