async這顆糖,很甜,也很鹹!

關於標題

爲何,async這顆糖很甜,也很鹹呢?
你們都知道,async函數是Generator函數的語法糖,那什麼是Generator函數呢? 若是你還沒了解Generator,Generator異步調用,這篇文章爲你介紹了Generator函數的原理和使用方法。
咱們先來看一個最簡單的Generator函數javascript

function * generator(x){
    var y  = yield x + 1;
    return y;
}
var gen = generator();
gen.next(1); //{value:2,done:false}
複製代碼

這個案例是最簡單的Generator函數的實現,咱們再來看一下,async函數的簡單例子java

async function generator(x){
    var y = await x + 1;
    return y;
    }
genertor(1).then(value => console.log(value)); //2
複製代碼

async真甜

對標Generator

對比上面兩段代碼,明顯感受到,使用async後,代碼變得更加清晰,同時,再也不須要手動調用next方法,就行實現異步加載,固然,爲了代碼清晰,上面的代碼並沒用異步調用,正常狀況下,await後面的表達式爲promise對象。
相對於Generator函數,async有下面幾點好處 :git

  1. 內置執行器。
  2. 更好的語義化。
  3. 更廣的適用性。
  4. 返回Promise對象。
    咱們分別來解釋一下上面的幾點好處 :
    第1點,內置執行器,在之前的文章咱們講到過,Generator函數若是想自動執行,須要引入CO模塊,而async沒必要,內置了執行器,可自動執行異步調用,得到結果後,再繼續執行。 第2點,更好的語義化,這個從字面上就能看得出來,很明顯,async函數更像是同步函數。
    第3點,更廣的適用性,看過Generator函數那節課的同窗都能知道,yield後面只能適用promise對象或者是thunk 函數(最新版本只能使用promise對象),async函數沒必要,就像例子,可接原始類型的值。
    第4點,很重要的一點。async函數返會的值是promise對象,仍是上面的例子,雖然return y,可是,運行函數後,返回的是promise對象。而Generator函數返回的是Iterator函數。若是有小夥伴不知道什麼事Iterator函數的,給你個連接Iterator函數。看完,你就知道,async函數有多好用。

用代碼說話

先定義一個 Fetch 方法用於獲取 github user 的信息:github

function fetchUser() { 
    return new Promise((resolve, reject) => {
        fetch('https://api.github.com/users/superman66')
        .then((data) => {
            resolve(data.json());
        }, (error) => {
            reject(error);
        })
    });
}
複製代碼

Promise 方式json

function getUserByPromise() {
    fetchUser()
        .then((data) => {
            console.log(data);
        }, (error) => {
            console.log(error);
        })
}
getUserByPromise();
複製代碼

使用promise後,代碼的執行變的很清晰,這能解決咱們當前問題,但有一種狀況,若是咱們的需求有屢次請求,切每次請求都須要上次請求的結果,這就變的很麻煩了。固然,有的小夥伴說了,那咱們就能夠繼續的then()下去啊,能夠,絕對能夠,可是,隨着請求次數的增多,代碼中出現更多數量的then,若是不加以註釋,也會變的晦澀難懂,這不是咱們想要的結果。繼續。。。 Generator 方式api

function* fetchUserByGenerator() {
    const user = yield fetchUser();
    return user;
}
const g = fetchUserByGenerator();
const result = g.next().value;
result.then((v) => {
    console.log(v);
}, (error) => {
    console.log(error);
})
複製代碼

Generator 的方式解決了 Promise 的一些問題,流程更加直觀、語義化。可是 Generator 的問題在於,函數的執行須要依靠執行器,每次都須要經過 g.next() 的方式去執行。
async 方式promise

async function getUserByAsync(){
     let user = await fetchUser();
     return user;
 }
getUserByAsync()
.then(v => console.log(v));
複製代碼

哇。。。一樣的結果,async像絲般順滑,搞定了。真甜。。。異步

關於async函數的返回值

前文說到,async函數返回一個Promise對象。async

async function generator(x){
    var y = await x + 1;
    return y;
    }
genertor(1).then(value => console.log(value)); //2
複製代碼

promise函數then方法的執行前提是,await後面的表達式以獲取到值。而後將值以參數的形式傳入後面的函數中(例子中直接在控制檯中打印出來)。 await後面能夠是promise對象,也能夠是基礎數據。async 函數返回的 Promise 對象,必須等到內部全部的 await 命令的 Promise 對象執行完,纔會發生狀態改變。函數

const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
    await delay(1000);
    await delay(2000);
    await delay(3000);
    return 'done';
}

f().then(v => console.log(v)); // 等待6s後才輸出 'done'
複製代碼

正常狀況下,await 命令後面跟着的是 Promise ,若是不是的話,也會被轉換成一個 當即 resolve 的 Promise。

async function f() {
    return await 1
};
f().then( (v) => console.log(v)) // 1
複製代碼

關於錯誤捕獲

若是await後面的異步操做出錯,那麼等同於async函數返回的 Promise 對象被reject。

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出錯了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯了
複製代碼

上面代碼中,async函數f執行後,await後面的 Promise 對象會拋出一個錯誤對象,致使catch方法的回調函數被調用,它的參數就是拋出的錯誤對象。 防止出錯的方法,也是將其放在try...catch代碼塊之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出錯了');
    });
  } catch(e) {
  }
  return await('hello world');
}
複製代碼

若是有多個await命令,能夠統一放在try...catch結構中。

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}
複製代碼

吃多可能也會鹹

再次解析一下async 函數的執行方式

當咱們在編寫JavaScript異步代碼的時候,人們常常在一個接着一個的函數調用前面添加await關鍵字.這會致使性能問題,由於在一般狀況下,一個語句的執行並不依賴前一個語句的執行,可是由於添加了await關鍵字,你仍舊須要等待前一個語句執行完才能執行一個語句.

(async () => {
  const pizzaData = await getPizzaData()    // async call
  const drinkData = await getDrinkData()    // async call
  const chosenPizza = choosePizza()    // sync call
  const chosenDrink = chooseDrink()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
  await addDrinkToCart(chosenDrink)    // async call
  orderItems()    // async call
})()
複製代碼

解釋: 1.得到披薩的列表. 2.得到飲料的列表. 3.從披薩列表中選擇披薩. 4.從飲料列表中選擇飲料. 5.把選擇的披薩加入購物車 6.把選擇的飲料加入購物車. 7.確認訂單 錯誤:得到披薩列表和飲料列表能夠同時進行,不必等待獲取披薩列表後再去獲取飲料列表。 如何解決這個問題呢?

async function selectPizza() {
  const pizzaData = await getPizzaData()    // async call
  const chosenPizza = choosePizza()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // async call
  const chosenDrink = chooseDrink()    // sync call
  await addDrinkToCart(chosenDrink)    // async call
}

(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // async call
})()

// 我更喜歡下面這種實現.
(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // async call
})()
複製代碼

很清晰的展示出,代碼的執行順序,相較於上一個,性能有了明顯的提升。 ##總結 仍是題目的那句話,async函數很甜,也很鹹。不要過度的去使用async/await,由於徹底不影響異步執行的操做,若是非要同步執行,隨着調用接口的增長,回嚴重影響性能。瑕不掩瑜,async的便利性,很大程度上會減小開發量,同時代碼的可讀性也有很大的提升。

若是你以爲這篇文章對你有幫助,別忘了給點個贊呦~ 每週會給你們分享一到兩篇文章,但願跟你們一塊兒學習,一塊兒進步。

相關文章
相關標籤/搜索