Promise 面試題來源網絡node
const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4)
運行結果:面試
1 2 4 3
解釋:Promise 構造函數是同步執行的,promise.then
中的函數是異步執行的。bootstrap
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) const promise2 = promise1.then(() => { throw new Error('error!!!') }) console.log('promise1', promise1) console.log('promise2', promise2) setTimeout(() => { console.log('promise1', promise1) console.log('promise2', promise2) }, 2000)
運行結果:promise
promise1 Promise { <pending> } promise2 Promise { <pending> } (node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!! (node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. promise1 Promise { 'success' } promise2 Promise { <rejected> Error: error!!! at promise.then (...) at <anonymous> }
解釋:promise 有 3 種狀態:pending、fulfilled 或 rejected。狀態改變只能是 pending->fulfilled 或者 pending->rejected,狀態一旦改變則不能再變。
上面 promise2 並非 promise1,而是返回的一個新的 Promise 實例。(在Node.js和瀏覽器console的結果,有些差別)瀏覽器
const promise = new Promise((resolve, reject) => { resolve('success1') reject('error') resolve('success2') }) promise .then((res) => { console.log('then: ', res) }) .catch((err) => { console.log('catch: ', err) })
運行結果:網絡
then: success1
解釋:構造函數中的 resolve 或 reject 只有第一次執行有效,屢次調用沒有任何做用,呼應代碼二結論:promise 狀態一旦改變則不能再變。異步
Promise.resolve(1) .then((res) => { console.log(res) return 2 }) .catch((err) => { return 3 }) .then((res) => { console.log(res) })
運行結果:函數
1 2
解釋:promise 能夠鏈式調用。提起鏈式調用咱們一般會想到經過 return this
實現,不過 Promise 並非這樣實現的。
promise 每次調用 .then
或者 .catch
都會返回一個新的 promise,從而實現了鏈式調用。性能
const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('once') resolve('success') }, 1000) }) const start = Date.now() promise.then((res) => { console.log(res, Date.now() - start) }) promise.then((res) => { console.log(res, Date.now() - start) })
運行結果:this
once success 1001 success 1001
解釋:promise 的 .then
或者 .catch
能夠被調用屢次,但這裏 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then
或者 .catch
都會直接拿到該值。(有些人的瀏覽器運行結果可能不是1001,那因爲電腦性能形成的微小差別)
Promise.resolve() .then(() => { return new Error('error!!!') }) .then((res) => { console.log('then: ', res) }) .catch((err) => { console.log('catch: ', err) })
運行結果:
then: Error: error!!! at Promise.resolve.then (...) at ...
解釋:.then
或者 .catch
中 return 一個 error 對象並不會拋出錯誤,因此不會被後續的 .catch
捕獲,須要改爲其中一種:
return Promise.reject(new Error('error!!!')) throw new Error('error!!!')
由於返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!')
等價於 return Promise.resolve(new Error('error!!!'))
。
const promise = Promise.resolve() .then(() => { return promise }) promise.catch(console.error)
運行結果:
TypeError: Chaining cycle detected for promise #<Promise> at <anonymous> at process._tickCallback (internal/process/next_tick.js:188:7) at Function.Module.runMain (module.js:667:11) at startup (bootstrap_node.js:187:16) at bootstrap_node.js:607:3
解釋:.then
或 .catch
返回的值不能是 promise 自己,不然會形成死循環。相似於:
process.nextTick(function tick () { console.log('tick') process.nextTick(tick) })
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log)
運行結果:
1
解釋:.then
或者 .catch
的參數指望是函數,傳入非函數則會發生值穿透。
Promise.resolve() .then(function success (res) { throw new Error('error') }, function fail1 (e) { console.error('fail1: ', e) }) .catch(function fail2 (e) { console.error('fail2: ', e) })
運行結果:
fail2: Error: error at success (...) at ...
解釋:.then
能夠接收兩個參數,第一個是處理成功的函數,第二個是處理錯誤的函數。.catch
是 .then
第二個參數的簡便寫法,可是它們用法上有一點須要注意:.then
的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,然後續的 .catch
能夠捕獲以前的錯誤。固然如下代碼也能夠:
Promise.resolve() .then(function success1 (res) { throw new Error('error') }, function fail1 (e) { console.error('fail1: ', e) }) .then(function success2 (res) { }, function fail2 (e) { console.error('fail2: ', e) })
process.nextTick(() => { console.log('nextTick') }) Promise.resolve() .then(() => { console.log('then') }) setImmediate(() => { console.log('setImmediate') }) console.log('end')
運行結果:
end nextTick then setImmediate
解釋:process.nextTick
和 promise.then
都屬於 microtask,而 setImmediate
屬於 macrotask,在事件循環的 check 階段執行。
事件循環的每一個階段(macrotask)之間都會執行 microtask,事件循環的開始會先執行一次 microtask。
var p = new Promise(function(resolve, reject){ resolve(1); }); p.then(function(value){ //第一個then console.log(value); return value*2; }).then(function(value){ //第二個then console.log(value); }).then(function(value){ //第三個then console.log(value); return Promise.resolve('resolve'); }).then(function(value){ //第四個then console.log(value); return Promise.reject('reject'); }).then(function(value){ //第五個then console.log('resolve: '+ value); }, function(err){ console.log('reject: ' + err); })
運行結果:
1 2 undefined "resolve" "reject: reject"
Promise對象的then方法返回一個新的Promise對象,所以能夠經過鏈式調用then方法。
then方法接收兩個函數做爲參數,第一個參數是Promise執行成功時的回調,第二個參數是Promise執行失敗時的回調。
兩個函數只會有一個被調用,函數的返回值將被用做建立then返回的Promise對象。
這兩個參數的返回值能夠是如下三種狀況中的一種:
return
一個同步的值 ,或者 undefined
(當沒有返回一個有效值時,默認返回undefined),then
方法將返回一個resolved狀態的Promise對象,Promise對象的值就是這個返回值。return
另外一個 Promise,then
方法將根據這個Promise的狀態和值建立一個新的Promise對象返回。throw
一個同步異常,then
方法將返回一個rejected狀態的Promise, 值是該異常。根據以上分析,代碼中第一個then
會返回一個值爲2(1*2),狀態爲resolved的Promise對象,因而第二個then
輸出的值是2。
第二個then
中沒有返回值,所以將返回默認的undefined,因而在第三個then
中輸出undefined。
第三個then
和第四個then
中分別返回一個狀態是resolved的Promise和一個狀態是rejected的Promise,依次由第四個then
中成功的回調函數和第五個then
中失敗的回調函數處理。
var p1 = new Promise( function(resolve,reject){ foo.bar(); resolve( 1 ); }); p1.then( function(value){ console.log('p1 then value: ' + value); }, function(err){ console.log('p1 then err: ' + err); } ).then( function(value){ console.log('p1 then then value: '+value); }, function(err){ console.log('p1 then then err: ' + err); } ); var p2 = new Promise(function(resolve,reject){ resolve( 2 ); }); p2.then( function(value){ console.log('p2 then value: ' + value); foo.bar(); }, function(err){ console.log('p2 then err: ' + err); } ).then( function(value){ console.log('p2 then then value: ' + value); }, function(err){ console.log('p2 then then err: ' + err); return 1; } ).then( function(value){ console.log('p2 then then then value: ' + value); }, function(err){ console.log('p2 then then then err: ' + err); } );
運行結果:
p1 then err: ReferenceError: foo is not defined p2 then value: 2 p1 then then value: undefined p2 then then err: ReferenceError: foo is not defined p2 then then then value: 1
Promise中的異常由then參數中第二個回調函數(Promise執行失敗的回調)處理,異常信息將做爲Promise的值。
異常一旦獲得處理,then返回的後續Promise對象將恢復正常,並會被Promise執行成功的回調函數處理。
另外,須要注意p一、p2 多級then的回調函數是交替執行的 ,這正是由Promise then回調的異步性決定的。
var p1 = Promise.resolve( 1 ); var p2 = Promise.resolve( p1 ); var p3 = new Promise(function(resolve, reject){ resolve(1); }); var p4 = new Promise(function(resolve, reject){ resolve(p1); }); console.log(p1 === p2); console.log(p1 === p3); console.log(p1 === p4); console.log(p3 === p4); p4.then(function(value){ console.log('p4=' + value); }); p2.then(function(value){ console.log('p2=' + value); }) p1.then(function(value){ console.log('p1=' + value); })
運行結果:
true false false false p2=1 p1=1 p4=1
Promise.resolve(...)能夠接收一個值或者是一個Promise對象做爲參數。
當參數是普通值時,它返回一個resolved狀態的Promise對象,對象的值就是這個參數;當參數是一個Promise對象時,它直接返回這個Promise參數。
所以,p1 === p2。但經過new的方式建立的Promise對象都是一個新的對象,所以後面的三個比較結果都是false。
另外,爲何p4的then最早調用,但在控制檯上是最後輸出結果的呢?
由於p4的resolve中接收的參數是一個Promise對象p1,resolve會對p1」拆箱「,獲取p1的狀態和值,但這個過程是異步的,可參考下一節。
var p1 = new Promise(function(resolve, reject){ resolve(Promise.resolve('resolve')); }); var p2 = new Promise(function(resolve, reject){ resolve(Promise.reject('reject')); }); var p3 = new Promise(function(resolve, reject){ reject(Promise.resolve('resolve')); }); p1.then( function fulfilled(value){ console.log('fulfilled: ' + value); }, function rejected(err){ console.log('rejected: ' + err); } ); p2.then( function fulfilled(value){ console.log('fulfilled: ' + value); }, function rejected(err){ console.log('rejected: ' + err); } ); p3.then( function fulfilled(value){ console.log('fulfilled: ' + value); }, function rejected(err){ console.log('rejected: ' + err); } );
運行結果:
p3 rejected: \[object Promise\] p1 fulfilled: resolve p2 rejected: reject
Promise回調函數中的第一個參數resolve,會對Promise執行"拆箱"動做。即當resolve的參數是一個Promise對象時,resolve會"拆箱"獲取這個Promise對象的狀態和值,但這個過程是異步的。p1"拆箱"後,獲取到Promise對象的狀態是resolved,所以fulfilled回調被執行;p2"拆箱"後,獲取到Promise對象的狀態是rejected,所以rejected回調被執行。但Promise回調函數中的第二個參數reject不具有」拆箱「的能力,reject的參數會直接傳遞給then方法中的rejected回調。所以,即便p3 reject接收了一個resolved狀態的Promise,then方法中被調用的依然是rejected,而且參數就是reject接收到的Promise對象。