ECMAScript 6 學習:Promise篇

 

Promise是異步編程的一種解決方案。Promise對象,可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。

從0到1實現Promise 這篇文章詳細地介紹了Promise的實現,值得細細研讀。

 

 

1、Promise 新建後就會立即執行

 

2、調用 resolve 或 reject 並不會終結 Promise 的參數函數的執行

// 調用resolve(1)以後,後面的console.log(2)還是會執行,並且會首先打印出來
new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

 

3、Promise 內部的錯誤不會影響到 Promise 外部的代碼

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);

// 瀏覽器打印出錯誤提示後,不會退出進程、終止腳本執行,2 秒之後還是會輸出123

// Uncaught (in promise) ReferenceError: x is not defined
// 123

 

4、finally 方法的回調函數不接受任何參數,本質上是 then 方法的特例

finally是某些庫對Promise實現的一個擴展方法,無論是resolve還是reject,都會走finally方法

下面是對finally方法的簡單實現:

MyPromise.prototype.finally = function(fn) {
    return this.then(value => {
       fn();
       return value;
    }, reason => {
        fn();
        throw reason;
    });
};

finally 方法總是返回原來的值

// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})

 

5、Promise.reject()方法的參數,會原封不動地作爲 reject 的理由,變成後續方法的參數

// Promise.reject方法的參數是一個thenable對象
// 執行以後,後面catch方法的參數不是reject拋出的“出錯了”這個字符串,而是thenable對象
const thenable = {
  then(resolve, reject) {
    reject('出錯了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

 

6、Generator 函數與 Promise 的結合

function getFoo() {
    // console.log('執行getFoo');
    return new Promise(function (resolve, reject) {
        resolve('foo');
    });
}

const g = function* () {
    try {
        // next方法將指針移到getFoo()才執行函數getFoo
        // 第一次調用next方法,執行到getFoo()結束,此時還沒有給foo賦值
        console.log(111);
        const foo = yield getFoo();
        // yield表達式本身沒有返回值,或者說總是返回undefined。
        // next方法可以帶一個參數,該參數就會被當作上一個yield表達式的返回值
        // 第二次調用next方法(此時帶參數'foo'),纔給foo賦值,並打印foo
        console.log(foo);
    } catch (e) {
        console.log(e);
    }
};

function run(generator) {
    const it = generator(); // 生成遍歷器對象

    function go(result) {
        // console.log('result', result);
        if (result.done) return result.value;

        return result.value.then(function (value) {
            // console.log('value', value);
            return go(it.next(value));
        }, function (error) {
            return go(it.throw(error));
        });
    }

    go(it.next());
}

run(g);

 

7、如何停止一個Promise鏈

假設這樣一個場景,我們有一個很長的Promise鏈式調用,這些Promise是依次依賴的關係,如果鏈條中的某個Promise出錯了,就不需要再向下執行了,默認情況下,我們是無法實現這個需求的,因爲Promise無論是then還是catch都會返回一個Promise,都會繼續向下執行thencatch。舉例:

new Promise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
}).catch()
  .then()
  .then()
  .catch()
  .then()

有沒有辦法讓這個鏈式調用在ERROR!!!的後面就停掉,完全不去執行鏈式調用後面所有回調函數呢?

我們自己封裝一個Promise.stop方法。

MyPromise.stop = function() {
  return new Promise(function() {});
};

stop中返回一個永遠不執行resolve或者rejectPromise,那麼這個Promise永遠處於pending狀態,所以永遠也不會向下執行thencatch了。這樣我們就停止了一個Promise鏈。

new MyPromise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
  MyPromise.stop();
}).catch()
  .then()
  .then()
  .catch()
  .then()

但是這樣會有一個缺點,就是鏈式調用後面的所有回調函數都無法被垃圾回收器回收。

 

8、如何解決Promise鏈上返回的最後一個Promise出現錯誤

看如下例子:

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
});

這裏a不存在,所以給a.b賦值是一個語法錯誤,onFulfilled回調函數是包在try...catch中執行的,錯誤會被catch到,但是由於後面沒有thencatch了,這個錯誤無法被處理,就會被Promise吃掉,沒有任何異常,這就是常說的Promise有可能會吃掉錯誤

那麼我們怎麼處理這種情況呢?

方法一

使用done()

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
}).done();

done()方法相當於一個catch,但是卻不再返回Promise了,注意done()方法中不能出現語法錯誤,否則又無法捕獲了。

done方法作爲Promise鏈式調用的最後一步,用來向全局拋出沒有被Promise內部捕獲的錯誤,並且不再返回一個Promise。一般用來結束一個Promise鏈。

下面是done()方法的簡單實現:

MyPromise.prototype.done = function() {
    this.catch(reason => {
        console.log('done', reason);
        throw reason;
    });
};

 

方法二

普通錯誤監聽windowerror事件可以實現捕獲

window.addEventListener('error', error => {
  console.log(error); // 不會觸發
});

Promise沒有被onRejected()處理的錯誤需要監聽unhandledrejection事件

window.addEventListener('unhandledrejection', error=>
{
    console.log(error); // 打印"Hello, Fundebug!"
});
 
function foo()
{
    Promise.reject('Hello, Fundebug!');
}
 
foo();

 

PromiseA+規範

JavaScript Promise迷你書(中文版)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章