Promise與async/await與Generator

Promise是什麼:

Promise是異步微任務(process.nextTick、Promise.then() catch() finally()等),用於解決異步多層嵌套回調的問題(回調地獄–小球做正方形路徑運動),讓代碼的可讀性更高、更容易維護

Promise使用:

Promise是ES6提供的一個構造函數,可以使用Promise構造函數new出一個實例,Promise構造函數接受一個函數作為參數這個函數有兩個參數,分別是resolvereject

resolve將Promise的狀態由等待變為成功(resolved),將異步操作的結果作為參數傳遞出去;

reject將Promise的狀態變為失敗(rejected),在異步操作失敗時調用,將異步操作報錯的錯誤作為參數傳遞過去。

實例創建完成後,可以使用then方法分別指定成功或者失敗的回調回調函數,也可以使用catch捕獲失敗,then和catch最終返回的也是一個Promise,所以可以鏈式調用

Promise的特點:

1.對象狀態不受外界影響(Promise對象代表一個異步操作,有三種狀態) – pending(等待狀態) – resolved(成功狀態) – rejected(失敗狀態)

2.一旦狀態改變,就不會再變化,任何時候都可以得到這個結果Promise對象狀態的改變只有兩種可能,

   pending => resolved (then第一個回調)和 pending => rejected(then第二個回調)

   (這兩個狀態為結束狀態,表示Promise的生命周期已結束)

3.resolve方法中的參數是then中回調函數的參數,reject方法中的參數是catch中的參數

4.then方法和catch方法返回的都是成功狀態的Promise(catch中throw返回失敗狀態的Promise)

Promise的其他方法:

Promise.finally():當promise狀態發生變化時執行(任何變化都執行),不變化不執行

Promise.resolve(value):返回成功狀態的Promise對象,並將value轉遞給對應的then方法

(當resolve函數接收的是promise對象時,後面的then會根據傳遞的promise對象的狀態變化決定執行哪一個回調)

Promise.reject():返回一個失敗狀態的Promise對象,並將給定的失敗信息傳遞給對應的處理方法

Promise.any():接受一個promise對象集合,當其中的第一個promise成功時,就返回那個成功的promise值,不會等待其他promise全部完成

Promise.all():返回一個新的promise對象,該promise對象在參數對象里接收多個promise對象,參數中的promise都成功時才會觸發成功,任意一個promise對象失敗都會觸發失敗

Promise.race():使用第一個返回的promise實例對象,成功就是成功,失敗就是失敗

Promise.allSettled():該方法的狀態無傳入promise的狀態無關,它永遠都是成功的,只會記錄下各個promise的表現

(Promise.all、Promise.race、Promise.allSettled傳入的若不是promise數組,會將其轉換成promise數組,任何可遍歷對象都可作為數組)

async/await:

async/await是基於Promise實現的,使得異步代碼看起來像同步代碼,是寫異步代碼的新方式

async/await實際上是Generator的語法糖。顧名思義,async關鍵字,代表後面的函數中有異步操作,await表示等待一個異步方法執行完成

聲明異步函數只要在普通函數前加上一個關鍵字async即可

async函數返回一個Promise對象,(若返回值不是Promise對象也會通過Promise.resolve() 封裝成 Promise 對象返回)因此async函數return返回的值可以通過then方法來接收

async function funcA() {
  return 'hello!';
}

funcA().then(value => {
  console.log(value);
})
// hello!

await就是異步等待,等待的是一個Promise,因此await後面應該是一個Promise對象,若不是,也會被轉成立即resolve的Promise

async函數被調用後就會開始執行,遇到await後就會等待其後面的異步操作執行完成,接着執行函數體後面的語句

總的來說:async函數調用不會造成代碼的阻塞,但是await會造成async函數內部代碼的阻塞

async function func() {
  console.log('async function is running!');
  const num1 = await 200;
  console.log(`num1 is ${num1}`);
  const num2 = await num1+ 100;
  console.log(`num2 is ${num2}`);
  const num3 = await num2 + 100;
  console.log(`num3 is ${num3}`);
}

func();
console.log('run me before await!');
// async function is running!
// run me before await!
// num1 is 200
// num2 is 300
// num3 is 400

func函數執行後先輸出了  『async function is running!』,接着遇到了await異步等待,函數返回執行後面的同步任務 ‘run me before await!’

同步執行完成後接着await等待的位置繼續執行。

可以說:async函數可以看作多個異步任務包裝成一個Promise對象,而await命令就是其內部的語法糖

await後面的Promise對象不會總是返回resolved狀態,只要一個await後面的Promsie狀態變成rejected,整個async都會中斷執行

為了避免此情況可以使用try…catch來封裝多個await:

async function func() {
  try {
    const num1 = await 200;
    console.log(`num1 is ${num1}`);
    const num2 = await Promise.reject('num2 is wrong!');
    console.log(`num2 is ${num2}`);
    const num3 = await num2 + 100;
    console.log(`num3 is ${num3}`);
  } catch (error) {
    console.log(error);
  }
}

func();
// num1 is 200
// 出錯了
// num2 is wrong!

async/await:使得異步代碼看起來像同步代碼

function sayHi(name) {
  return new Promise((resolved, rejected) => {
    setTimeout(() => {
      resolved(name);
    }, 2000)
  })
}

async function sayHi_async(name) {
  const sayHi_1 = await sayHi(name)
  console.log(`你好, ${sayHi_1}`)
  const sayHi_2 = await sayHi('李四')
  console.log(`你好, ${sayHi_2}`)
  const sayHi_3 = await sayHi('王二麻子')
  console.log(`你好, ${sayHi_3}`)
}

sayHi_async('張三')
// 你好, 張三
// 你好, 李四
// 你好, 王二麻子

Generator:

generator(生成器)是ES6標準引入的新的數據類型。一個generator看起來像個函數,但可以返回多次。

ES6定義的generator是借鑒了python中的generator概念和語法

函數的概念:一個函數是一段完整的代碼,調用一個函數就是傳入函數,然後返回結果

函數在執行的過程中,如果沒有遇到return語句(沒有return,就是隱含return undefined),控制權無法交回給被調用的代碼

function foo(x) {
    return x + x;
}

var r = foo(1); // 調用foo函數

而generator定義如下:

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}

generator由 function* 定義,並且除了return語句外可以通過 yield 返回多次

舉個例子:斐波那契數列

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

調用generator和函數不同,有兩種調用方法,一是調用generator的 next() 方法:

var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

next()方法會執行generator代碼,每次遇到 yield a;就會返回一個對象{value: a, done: false}。每次返回的value就是yield的返回值,done表示這個generator是否已經執行結束

二是直接使用 for…of 循環迭代generator

for (var x of fib(10)) {
    console.log(x); // 依次輸出0, 1, 1, 2, 3, ...
}

generator的用處:

由於generator在執行的過程中可以返回多次,所以他看上去像一個可以記住執行狀態的函數,利用這一點,通過generator可以實現需要用面向對象才能實現的功能

generator還可以把異步代碼變成 「同步」 代碼

try {
    r1 = yield ajax('//url-1', data1);
    r2 = yield ajax('//url-2', data2);
    r3 = yield ajax('//url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

 

new關鍵字的執行過程:(構造函數為Func)

  • 創建一個空對象,並將該空對象繼承Func.prototype
  • 執行構造函數,並將this指向剛剛創建的新對象
  • 返回新對象

參考:

//segmentfault.com/a/1190000015488033

//www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112#0

Tags: