Promise 概述

weixin_33912246發表於2018-06-02

關於Promise

  • Promise例項一旦被建立就會被執行
  • Promise過程分為兩個分支:pending=>resolvedpending=>rejected
  • Promise狀態改變後,依然會執行之後的程式碼:
const warnDemo = ctx => {
  const promise = new Promise(resolve => {
    resolve(ctx);
    console.log("After resolved, but Run"); // 依然會執行這個語句
  });
  return promise;
};

warnDemo("ctx").then(ctx => console.log(`This is ${ctx}`));

then方法

Console鍵入以下內容:

let t = new Promise(()=>{});
t.__proto__

可以看到,then方法是定義在原型物件Promise.prototype上的。

then方法的第一個引數是resolved狀態的回撥函式,第二個引數(可選)是rejected狀態的回撥函式。

寫法

function func(args) {
    // 必須返回一個Promise例項
    return new Promise((resolve,reject)=>{
        if(...){
            resolve(...) // 傳入resolve函式的引數
        } else {
            let err = new Error(...) 
            reject(err) // reject引數必須是Error物件
        }
    })
}

func(ARGS).then(()=>{
    // resolve 函式
},()=>{
    // reject 函式
})

連續呼叫then

因為then方法返回另一個Promise物件。當這個物件狀態發生改變,就會分別呼叫resolvereject

寫法如下:

func(ARGS).then(()=>{
    ...
}).then(
    ()=>{ ... },
    () => { ... }
)

例項

function helloWorld(ready) {
    return new Promise((resolve,reject)=>{
        if (ready){
            resolve("Right") 
        } else {
            let error = new Error("arg is false")
            reject(error) // 傳入Error物件
        }
    })
}

helloWorld(false).then((msg)=>{ // true:helloWorld的引數
    // 引數msg:在上面的Promise物件中傳入了
    console.log(msg)
},(error)=>{
    console.log(error.message)
})

catch方法

等同於 .then(null, rejection)。另外,then方法指定的回撥函式執行中的錯誤,也會被catch捕獲。

所以,之前的寫法可以改為:

function func(args) {
    // 必須返回一個Promise例項
    const promise =  new Promise((resolve,reject)=>{
        if(...){
            resolve(...) 
        } else {
            let err = new Error(...) 
            reject(err) 
        }
    })
    return promise
}

func(ARGS).then(()=>{
    // resolve 函式
}).catch(()=>{
    // reject 函式
}).then(()=>{
    // 沒有錯誤就會跳過上面的catch
})...

finally方法

指定不管 Promise 物件最後狀態如何,都會執行的操作。可以理解為then方法的例項,即在resolvereject裡面的公共操作函式

all方法

用於將多個 Promise 例項,包裝成一個新的 Promise 例項。它接收一個具有Iterator介面的引數。其中,item如果不是Promise物件,會自動呼叫Promise.resolve方法

以下程式碼:

const p = Promise.all([p1, p2, p3]); // p是新包裝好的一個Promise物件

對於Promise.all()包裝的Promise物件,只有例項的狀態都變成fulfilled

可以用來運算元據庫:

const databasePromise = connectDatabase();

const booksPromise = databasePromise
  .then(findAllBooks);

const userPromise = databasePromise
  .then(getCurrentUser);

Promise.all([
  booksPromise,
  userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));

或者其中有一個變為rejected,才會呼叫Promise.all方法後面的回撥函式。而對於每個promise物件,一旦它被自己定義catch方法捕獲異常,那麼狀態就會更新為resolved而不是rejected

'use strict'
const p1 = new Promise((resolve, reject) => {
    resolve('hello');
}).then(
    result => result
).catch(
    e => e
);

const p2 = new Promise((resolve, reject) => {
    throw new Error("p2 error")
}).then(
    result => result
).catch( 
    // 如果註釋掉 catch,進入情況2
    // 否則,情況1
    e => e.message
);

Promise.all([p1, p2]).then(
    result => console.log(result) // 情況1
).catch(
    e => console.log("error in all") // 情況2
);

race方法

all方法類似,Promise.race方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項。而且只要有一個狀態被改變,那麼新的Promise狀態會立即改變

也是來自阮一峰大大的例子,如果5秒內無法fetech,那麼p狀態就會變為rejected

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));

重要性質

狀態只改變一次

Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。

下面程式碼中,Promise物件resolved後,狀態就無法再變成rejected了。

'use strict'

const promise = new Promise((resolve,reject)=>{
    resolve('ok') // 狀態變成 resolved
    throw new Error("test") // Promise 的狀態一旦改變,就永久保持該狀態
})
promise.then((val)=>{
    console.log(val)
}).catch((error)=>{
    console.log(error.message) // 所以,無法捕獲錯誤
})

錯誤冒泡

Promise 物件的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲

"吃掉錯誤"機制

Promise會吃掉內部的錯誤,並不影響外部程式碼的執行。所以需要catch,以防丟掉錯誤資訊。

阮一峰大大給出的demo:

'use strict'

const someAsyncThing = function() {
    return new Promise(function(resolve, reject) {
        // 下面一行會報錯,因為x沒有宣告
        resolve(x + 2);
    });
};
  
someAsyncThing().then(function() {
    console.log('everything is great');
});
  
setTimeout(() => { console.log(123) }, 2000);

還有如下demo

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會報錯,因為y沒有宣告
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

參考

  • demo基本可以在阮一峰的Es6講解中找到,只是為了理解做了一些修改。
  • 還有網上的一些部落格,這裡就不一一說明了

歡迎技術交流,引用請註明出處。
個人網站:godbmw.com
Github:godbmw

相關文章