Promise與async/await與Generator

Du9191 發表於 2022-05-08
人工智慧

Promise是什麼:

Promise是非同步微任務(process.nextTick、Promise.then() catch() finally()等)

用於解決非同步多層巢狀回撥的問題(回撥地獄--小球運動),讓程式碼的可讀性更高、更容易維護

小球運動:一個小球元素使其按照右下左上的路徑運動,點選後觸發回撥函式,如果不使用Promise那麼就要層層巢狀回撥函式

運動到右後 再到下 再到左 再到上,而此時想要修改運動路徑的話,修改這一層又一層的程式碼會非常繁瑣,而Promise的then()鏈就可以很好的解決這個問題

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('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

 

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

  • 建立一個空物件,並將該空物件繼承Func.prototype
  • 執行建構函式,並將this指向剛剛建立的新物件
  • 返回新物件

參考:

https://segmentfault.com/a/1190000015488033

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