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
,都會繼續向下執行then
或catch
。舉例:
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
或者reject
的Promise
,那麼這個Promise
永遠處於pending
狀態,所以永遠也不會向下執行then
或catch
了。這樣我們就停止了一個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
到,但是由於後面沒有then
或catch
了,這個錯誤無法被處理,就會被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;
});
};
方法二
普通錯誤監聽window
的error
事件可以實現捕獲
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();