基礎輸出題
- 題目1
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
記住 new Promise 里的參數函數,是同步被執行的,故而先輸出 1,2.
resolve 后還需要等待進入下一個事件循環。then 把參數函數推入微任務隊列,並不直接執行。
輸出 4,接着事件循環進入下一輪,輸出 3.
1
2
4
3
- 題目2
來自網易。給出一個 promise
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
resolve(1);
}, 3000)
})
請問這三種有何不同?
// 1
promise.then(() => {
return Promise.resolve(2);
}).then((n) => {
console.log(n)
});
// 2
promise.then(() => {
return 2
}).then((n) => {
console.log(n)
});
// 3
promise.then(2).then((n) => {
console.log(n)
});
- 輸出2。Promise.resolve 就是一個 Promise 對象就相當於返回了一個新的 Promise 對象。然后在下一個事件循環里才會去執行 then
- 輸出2。和上一點不一樣的是,它不用等下一個事件循環。
- 輸出1。then 和 catch 期望接收函數做參數,如果非函數就會發生 Promise 穿透現象,打印的是上一個 Promise 的返回。
- 題3
來自快手的變態題目,好討厭。得配合事件循環機制來看
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a);
console.log('after1');
await a
resolve(true);
console.log('after2');
});
console.log('end');
我直到現在也沒有想明白其中的一步,先貼答案
promise1
undefined
end
promise2
promise3
promise4
Promise { pending }
after1
第一個輸出 promise1,是因為 Promise 里的方法立即執行。接着調用 resolve,只不過 then 里的方法等下一個周期
第二個輸出 undefined,是因為立即執行執行 a 內部的方法,先 console.log(a),但此時的 a 還沒賦值給左邊的變量,所以只能是 undefined。然后 await b 就得等下一個周期執行了。
第三個輸出 end,自然不意外。
接着輸出 promise2,promise3,promise4,是因為 await b 等待他執行完了,才輪到 a 內部繼續執行。
輸出 Promise { pending },腦筋轉了以下才想通,事件都進入了循環了,a 肯定已經被賦值成了 Promise 對象。所以第二遍 console.log(a),自然就輸出這個了。
輸出 after1 不奇怪。
但是隨后的 await a 是個什么奇怪的操作,想半天沒搞懂為何最后不輸出 after2,調試得知根本就執行不到 await a 以后的代碼上,想不懂。
更新:和不少朋友交流后,我得出了結論,await a 時,a 是必須等待 Promise 的狀態從 pending 到 fullfilled 才會繼續往下執行,可 a 的狀態是一直得不到更改的,所以無法執行下面的邏輯。只要在 await a 上面加一行 resolve() 就能讓后面的 after 2 得到輸出。
- 題4
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);
});
簡單題,牢牢記住 Promise 對象的狀態只能被轉移一次,resolve('success1') 時狀態轉移到了 fullfilled 。后面 reject 就調用無效了,因為狀態已經不是 pending。
- 題5
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
沒有拋出錯誤和異常,只是 return 了一個對象,哪怕他是 Error 對象,那自然也是正常走 then 的鏈式調用下去了,不會觸發 catch。
手寫 Promise
曾經實現過,掛在博客上JS 來實現一個 Promise
簡述:內部有個 state,doneList,failList,方法 resolve,reject,返回 fn(resolve, reject)。原型鏈有個 then,里面做的事情就是把函數參數推入 doneList 和 failList。然后 resolve 里執行的邏輯就是拿出 doneList 順序執行下去,reject 執行 failList。
怎么取消掉 Promise
給你一個 promise,但是希望取消掉它。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
axios 倒是有 abort 方法,但面試里你肯定不得用。
promise 其實缺陷就是無法得知執行到了哪兒,也無法取消,只能被動的等 resolve 或者 reject 執行或者拋錯。所以思路就是外部包裹一層 Promise,並對外提供 abort 方法,這個 abort 方法可以用來 reject 內部的 Promise 對象。
function wrap(p) {
let resol = null;
let abort = null;
let p1 = new Promise((resolve, reject) => {
resol = resolve;
abort = reject;
});
p1.abort = abort;
p.then(resol, abort);
return p1;
}
let newPromise = wrap(promise);
newPromise.then(res => console.log)
newPromise.abort()
順序輸出
不使用 async/await,給你若干個 promise 對象,你怎么保證它是順序執行的?
var makePromise = function(value, time) {
return new Promise(function(resolve, reject){
setTimeout(function() {
resolve(value);
}, time)
})
};
function order(promises) {
}
order([
makePromise('a', 3000),
makePromise('b', 5000),
makePromise('c', 2000),
]);
思路是 then 里返回下一個
function order(promises) {
let dataArr = []
let promise = Promise.resolve();
for (let i = 0; i < promises.length; i++) {
promise = promise.then((data) => {
if (data) {
dataArr.push(data);
console.log(data);
}
return promises[i];
});
}
return promise.then(data => {
console.log(data);
})
}
其實這么做有個問題,數組里的 promise 三項其實還是並發的,只不過輸出的順序的確符合期望,但是間隔的時間會比較奇怪,3s 后輸出 a,再間隔 2s 連着輸出了 b 和 c。
要想讓間隔時間也完全符合串行的話,那么還是在 then 里制造下一個 Promise 對象並返回吧。
並發做異步請求,限制頻率
舉個例子,有 8 張圖片 url,你需要並發去獲取它,並且任何時刻同時請求的數量不超過 3 個。也就是說第 4 張圖片一定是等前面那一批有一個請求完畢了才能開始,以此類推。
var urls = [
'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg',
'https://www.kkkk1000.com/images/getImgData/gray.gif',
'https://www.kkkk1000.com/images/getImgData/Particle.gif',
'https://www.kkkk1000.com/images/getImgData/arithmetic.png',
'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif',
'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg',
'https://www.kkkk1000.com/images/getImgData/arithmetic.gif',
'https://www.kkkk1000.com/images/wxQrCode2.png',
];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
console.log('一張圖片加載完成', url);
resolve();
};
img.onerror = reject;
img.src = url;
});
}
function limitload(urls, limit) {
}
limitload(urls, 2);
解法就是前 3 個放入一個外部的 promise 里同時進行,then 方法指定去請求后面的圖片。
function limitload(urls, limit) {
let index = limit;
function execNewPromise() {
index += 1;
if (index < urls.length) {
return loadImg(urls[index]).then(() => execNewPromise());
}
}
var promise = Promise.resolve();
promise.then(() => {
for (let i = 0; i < limit; i++) {
loadImg(urls[i]).then(() => execNewPromise());
}
});
}
總結
單單能說事件循環機制本身還是不夠,總有面試問題會非常惡心人,還是得多做幾道備用,這個體驗挺糟糕的,比讓我手寫 leetcode 算法題還難受。畢竟誰的代碼會寫成那樣呢。
要我手寫 Promise 的公司,近兩個月里出現了兩家。第一次被問到時,雖然很久以前我寫過並放入博客中,但真的上手寫時還是很容易卡殼的,大家小心。