從promise到asycn/await

1、什麼是promise?

在MDN中,定義promise的只有一句話:promise對象用於表示一個異步操做的最終完成(或失敗),及其結果值。javascript

從這句話的定義咱們能夠抓住幾個關鍵詞:promise是對象、異步操做、最終狀態及結果值。java

在真正瞭解promise是什麼前,咱們不得不思考,promise的出現到底是爲了解決什麼問題。ajax

背景promise

javascript是單線程語言:單線程指若是有多個任務必須先排隊,前面的任務執行完成後,後面的任務再執行。瀏覽器

同步

若是在函數返回結果的時候,調用者可以拿到預期的結果(就是函數計算的結果),那麼這個函數就是同步函數。bash

console.log('joseydong'); // 執行後,得到了返回結果

function wait(){
    var time = (new Date()).getTime();//獲取當前的unix時間戳
    while((new Date()).getTime() - time < 5000){}
    console.log('你好個人名字是');
}
wait();
console.log('josey');
console.log('說得太慢了');
複製代碼

在這段代碼中,wait函數是一個須要耗時5秒的函數,在這5秒中,下面的console.log()函數只能等待。異步

這就是同步的缺點:若是一個函數是同步的,即便調用函數執行任務比較耗時,也會一直等待直到獲得執行結果。async

異步

若是在函數返回的時候,調用者還不能獲得預期的結果,而是未來經過必定的手段獲得,這就叫異步。函數

當執行異步函數的時候,發出調用以後會立刻返回,但不會是返回預期結果;調用者沒必要默默等待,當獲得返回結果的時候回經過回調函數主動通知調用者。優化

異步的實現

異步操做是會在某個時間點觸發一個函數的調用。

好比AJAX就是典型的異步操做:

request.onreadystatechange = function () {
    if (request.readyState === 4) {
        if (request.status === 200) {
            return success(request.responseText);
        } else {
            return fail(request.status);
        }
    }
}
複製代碼

把回調函數success(request.responseText)和fail(request.status)寫在一個ajax操做裏,不利於維護,也很差複用。

思考一種更好的寫法,好比:

var ajax = ajaxGet('url');
ajax.ifSuccess(success)
    .ifFail(fail)
複製代碼

這種鏈式寫法的好處在於:先統一執行ajax邏輯,不關心如何處理返回結果,而後根據返回結果是成功仍是失敗,在未來的某個時候調用success函數或者fail函數。

promise

這種承諾(promise)未來會執行的對象被稱爲promise對象。

Promise有各類開源實現,在ES6中被統一規範,由瀏覽器直接支持。

2、關於promise

promise是一個對象,從這個對象中能夠獲取異步操做的信息。

它表明一個異步操做,有三種狀態:

  • pending(進行中):初始狀態
  • resolved(已完成):操做成功。又名fulfilled
  • rejected(已失敗):操做失敗

有了promise,就能夠將異步操做以同步操做的流程表達,但它也有以下缺點:

  • 一旦建立就會當即執行,沒法中途取消
  • 若是不設置回調函數,promise內部拋出的錯誤,不會反映到外部
  • 當處於pending狀態,沒法得知是剛剛開始還全是即將結束。

3、應用示例

建立promise

var promise = new Promise(
	/* executor */
	function(resolve,reject){
  		//...
	}
);
複製代碼

executor函數由Promise實例當即執行,傳遞resolved和reject函數。

在executor內部,promise有以下可能的變化:

  • resolve被調用,promise狀態由pengding變爲resolved,表明該promise被成功解析
  • reject被調用,promise由pengding變爲rejected,表明該promise的值不能用於後續處理,被拒絕了。

一、若是在executor方法的執行過程當中拋出了任何異常,那麼promise當即被拒絕,至關於reject方法被調用,executor的返回值也就被忽略。

二、若是一個promise對象處在resolved或者rejected狀態,那麼也能夠被稱爲settled狀態。

處理Promise實例

  • then:Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。 promise.then(value => { // success 狀態爲resolved時調用 },error => { // failure 狀態爲rejected時調用 }); then方法能夠接受兩個回調函數做爲參數: 第一個回調函數:在pending狀態—>resolved狀態時被調用;參數爲resolve方法的返回值 第二個回調函數:在pending狀態—>rejected狀態時被調用;參數爲reject方法的返回值;可選。
  • catch:Promise.prototype.catch方法是.then(null,rejection)的別名,用於指定發生錯誤時的回調函數。 promise.then(res => { // ... }).catch(error => { console.log('發生錯誤:',error); });
  • 擴展 Promise.prototype.then和Promise.prototype.catch方法返回promise對象,因此它能夠被鏈式調用。但此時返回的是以函數返回值生成的新的Promise實例,不是原來的那個Promise實例。

1.Promise對象的錯誤具備冒泡性質,會一直向後傳遞,直到被捕獲爲止。也就是說錯誤必定會被catch語句捕獲。

將多個Promise實例,包裝成一個新的Promise實例

  • Promise.all(iterable):當全部在可迭代參數中的promises已完成時,或者當傳遞過程當中的任何一個promise進入rejected狀態,返回promise。 var promise = Promise.all([p1,p2,p3]);

    p1&&p2&&p3 都返回resolved => promise返回resolved
    
       p1||p2||p3中任意一個返回rejected=>promise狀態就變成rejected,此時第一個被reject的實例返回值會傳遞給p的回調函數。
    複製代碼

4、promise最佳實踐

  • 防止then嵌套 由於then中return的仍是promise,因此會執行完裏面的promise再執行外面的then。此時最好將其展開,也是同樣的結果,並且會更好讀。

    // ------------    很差的寫法   -------------
    new Promise (resolve => {
        console.log('Step 1');
        setTimeout(()=> {
            resolve('100');
        },1000)
    }).then(value => { // value => 100
        return new Promise(resolve => {
            console.log('Step 1-1');
            setTimeout(() => {
                resolve('110');
            },1000);
        })
        .then(value => { // value => 110
            console.log('Step 1-2');
            return value;
        })
        .then(value => { // value => 110
            console.log('Step 1-3')
            return value;
        })
    })
    .then(value => {
        console.log(value); // value = 110
        console.log('Step 2')
    })
    複製代碼
    // ------------    好的寫法    ------------
    
    new Promise((resolve) => {
    
    console.log("Step 1");
    setTimeout(() => {
      resolve("100");
    }, 1000);
    
    })
    
    .then((value) => {
      // value => 100
      return new Promise((resolve) => {
        console.log("Step 1-1");
        setTimeout(() => {
          resolve("110");
        }, 1000);
      });
    })
    .then((value) => {
      // value => 110
      console.log("Step 1-2");
      return value;
    })
    .then((value) => {
      // value => 110
      console.log("Step 1-3");
      return value;
    })
    .then((value) => {
      console.log(value); // value = 110
      console.log("Step 2");
    });
    複製代碼
  • 使用.catch()捕捉錯誤

    一般狀況下promise有以下兩種處理方式:

    // ------------ 很差的寫法 -------------
    promise.then(function(data) {
        // success
      }, function(err) {   //僅處理promise運行時發生的錯誤。沒法處理回調中的錯誤
        // error
      });
    
    // ------------ 好的寫法 ------------
    promise.then(res => {
        // success
    }).catch(err => {   // 處理 promise 和 前一個回調函數運行時發生的錯誤
        // error 
    });
    
    複製代碼

由於promise拋出的錯誤不會傳遞到外層,當使用第一種寫法時,成功回調的錯誤沒法處理,所以建議使用catch方法。

  • then方法中,永遠return或throw //------------ 很差的寫法 ------------------ promise.then(function () { getUserInfo(userId); }).then(function () { // 在這裏可能但願在這個回調中使用用戶信息,但你可能會發現它根本不存在 }); 若是要使用鏈式then,必須返回一個promise對象。

5、async/await是什麼?

async:異步,await:異步等待。

簡單來講async用於聲明一個function是異步的,而await用於等待一個異步方法執行完成。

語法規定await只能出如今async函數中,async函數返回的是一個Promise對象。

promise的特色是無需等待,因此在沒有await的狀況下執行async函數,它會當即執行,返回一個promise對象而且不會阻塞後面的語句,就和普通的promise同樣。

await等待的是一個promise對象/其餘值。

由於async函數返回的是一個promise對象,因此await能夠用於等待一個async函數的返回值。而且,它能夠等待任意表達式的結果,因此await後面能夠接普通函數調用或者直接量。

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething(); // Promise對象
    const v2 = await testAsync(); // 普通函數
    console.log(v1, v2);
}

test();
複製代碼

await是個運算符,用於組成表達式,await表達式的運算結果取決於它等的東西。

若是等到的不是個promise對象,那麼await表達式的運算結果就等於它等到的東西。

若是等到的是個promise對象,那麼await就會阻塞後面的代碼,等着promise對象的resolved狀態,而後獲得resolve的值,做爲await表達式的運算結果。

一、這就是await必須放在async函數內部的緣由:async函數調用不會形成阻塞,它內部全部的阻塞都被封裝在一個promise對象中異步執行。

6、asycn/await與promise簡單比較

不使用async/await

function takeLongTime() {
  return new Promise(resolve => {
    setTimeout(() => resolve('hahahha'),1000);
  });
}

takeLongTime().then(value => console.log('heihei',value))
複製代碼

使用async/await

function takeLongTime() {
  return new Promise(resolve => {
    setTimeout(() => resolve('hahahha'),1000);
  });
}

async function test() {
  const value = await takeLongTime();
  console.log(value);
}

test();
複製代碼

async/await的優點在於處理then鏈

Promise經過then鏈來解決多層回調問題,但若是須要同時處理由多個Promise組成的then鏈,就能夠用async/await來進一步優化。

假設一個場景,分多個步驟完成,每一個步驟都是異步的,並且依賴於上一個步驟的結果。

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
複製代碼

用Promise方法來實現這幾個步驟的處理:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1580.487ms
複製代碼

用async/await來實現:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();
複製代碼

一、async/await優勢:代碼更加簡潔。

修改下上面場景的代碼,後續步驟須要多個返回值:

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}
複製代碼

用promise處理,就會發現處理返回值會比較麻煩:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();
複製代碼

用async/await處理:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
複製代碼

二、async/await的優勢:解決promise傳遞參數太麻煩的問題。

async/await使用try...catch處理rejected狀態

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

// 另外一種寫法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}
複製代碼
相關文章
相關標籤/搜索