Promise 做爲當下主流的異步解決方案,在工做中和麪試中經常出現,尤爲是在面試中,會弄個場景讓你手寫代碼,這裏給你們介紹五道比較有表明性的題目,以便熟悉一些套路。javascript
先簡單介紹下 Promisejava
Promise 對象用於表示一個異步操做的最終完成 (或失敗), 及其結果值。能夠爲異步操做的成功和失敗綁定執行函數,讓異步方法能夠像同步方法同樣返回值,但當即返回的是一個能表明將來可能出現結果的Promise對象。面試
Promise 對象有三種狀態:api
Promise 的使用和提供的靜態方法:數組
new Promise( function(resolve, reject) {...} /* executor */ );
:返回 Promise 對象Promise.all(iterable)
:iterable參數對象裏全部的promise對象都成功的時候纔會觸發成功,若一個失敗,則當即觸發返回Promise對象的失敗Promise.race(iterable)
:iterable參數中的一個成功或者失敗都會當即觸發返回對象的成功和失敗Promise.reject(reason)
:返回一個狀態爲失敗的Promise對象Promise.resolve(value)
:返回一個狀態由value給定的Promise對象,一般用於將一個值以Promise的方式使用。下面開始看題promise
與js事件循環結合出題,以下,寫出執行結果併發
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {console.log('async2 end')}
async1()
setTimeout(function () {console.log('setTimeout')}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
}).then(function () {
console.log('promise1')
}).then(function () {
console.log('promise2')
})
console.log('script end')
// 結果以下
// script start
// async2 end
// Promise
// script end
// async1 end
// promise1
// promise2
// setTimeout
複製代碼
掌握事件循環機制和明白 Promise.then()
屬於微隊列,這一類的題目就都是一個套路。dom
實現以下調用,lazyMan('xxx').sleep(1000).eat('333').sleepFirst(2000)
sleepFirst 最早執行。異步
這題考察如何組合多個 Promise 和鏈式調用。async
能夠用數組將 sleep eat 等函數暫存,同時爲了能鏈式調用,因此每一個函數需返回 Promise 對象。那麼何時執行數組中的函數呢?
根據事件循環機制,咱們用 setTimeout 來執行數組中的方法,在定時器的回調函數中相關的事件已經添加到數組中了,鏈式執行數組中方法前,須要有一個構建一個 Promise
對象來執行 then
方法,能夠經過 Promise.resolve()
返回一個 Promise 對象。
function lazyMan(name) {
this.task = [];
this.task.push(() => {
return new Promise(res => {
console.log('name:' + name);res()
})
})
let run = () => {
let sequence = Promise.resolve()
for (const func of this.task) {
sequence = sequence.then(()=>func())
}
}
setTimeout(() => {run()}, 0)
this.eat = (str) => {
this.task.push(() => {
return new (res => {
console.log('eat:' + str);res()
})
})
return this;
}
this.sleep = (time) => {
this.task.push(() => {
return new Promise(res => {
setTimeout(() => {
console.log(`Wake up after ` + time);res()
}, time)
})
})
return this;
}
this.sleepFirst = (time) => {
this.task.unshift(() => {
return new Promise(res => {
setTimeout(() => {
console.log(`sleepFirst up after ` + time);res()
}, time)
})
})
return this;
}
return this;
}
複製代碼
任務隊列可不斷的添加異步任務(異步任務都是Promise),但只能同時處理5個任務,5個一組執行完成後才能執行下一組,任務隊列爲空時暫停執行,當有新任務加入則自動執行。
class RunQune{
constructor(){
this.list = []; // 任務隊列
this.target = 5; // 併發數量
this.flag = false; // 任務執行狀態
this.time = Date.now()
}
async sleep(time){
return new Promise(res=>setTimeout(res,time))
}
// 執行任務
async run(){
while(this.list.length>0){
this.flag = true;
let runList = this.list.splice(0,this.target);
this.time = Date.now()
await this.runItem(runList)
await this.sleep(300) // 模擬執行時間
}
this.flag = false;
}
async runItem(list){
return new Promise((res)=>{
while(list.length>0){
const fn = list.shift();
fn().then().finally(()=>{
if(list.length === 0){
res()
}
})
}
})
}
// 添加任務
push(task){
this.list.push(...task);
!this.flag && this.run()
}
}
複製代碼
這題還能夠進一步發散,不須要等待一組完成在執行下一組,只要併發量沒有滿,就能夠加入新的任務執行,實現的思路沒太大變化,在 finally 中改成新增任務。
指望id按順序打印 0 1 2 3 4
,且只能修改 start
函數。
function start(id) {
execute(id)
}
for (let i = 0; i < 5; i++) {
start(i);
}
function sleep() {
const duration = Math.floor(Math.random() * 500);
return new Promise(resolve => setTimeout(resolve, duration));
}
function execute(id) {
return sleep().then(() => {
console.log("id", id);
});
}
複製代碼
id 的打印是個異步事件,在 setTimeout 回調執行,按照上面的代碼,誰的倒計時先結束,id就先打印,那麼想要id按順序打印,就須要將多個異步事件同步執行,promise 的鏈式調用能夠派上用場。代碼以下
function start(id) {
// execute(id)
// 第一種:promise 鏈式調用,execute 函數返回的就是 promise ,因此能夠利用這一點,經過 promise.then 依次執行下一個打印
this.promise = this.promise ? this.promise.then(()=>execute(id)) : execute(id)
// 第二種:先用數組存儲異步函數,利用事件循環的下一個階段,即 setTimeout 的回調函數中執行 promise 的鏈式調用,這方法本質上和第一種是同樣的
this.list = this.list ? this.list : []
this.list.push(() => execute(id))
this.t;
if (this.t) clearTimeout(this.t)
this.t = setTimeout(() => {
this.list.reduce((re, fn) => re.then(() => fn()), Promise.resolve())
})
// 第三種:數組存儲id的值,在經過 await 異步執行 execute 函數
this.list = this.list ? this.list : []
this.list.push(id)
clearTimeout(this.t)
this.t = setTimeout(async () => {
let _id = this.list.shift()
while (_id !== undefined) {
await execute(_id);
_id = this.list.shift()
}
})
}
複製代碼
手撕源碼系列,來手寫一個Promise,在動手前須要先了解 Promise/A+ 規範,列舉關鍵部分的規範,詳細規範可見文末連接
onFulfilled, onRejected
,onFulfilled 和 onRejected 必須被做爲函數調用,且調用不可超過1次。 then 方法需返回 Promise 對象根據這三點我實現了一個簡化版的 Promise
function MPromise(executor) {
this.status = 'pending'; // pending , fulfilled , rejected
this.data = '' // 當前promise的值,主要用於 then 方法中的 fulfilled , rejected 兩種狀態的處理
this.resolveFuncList = []; // 使用數組的緣由是,一個promise能夠同時執行多個 then 方法, 也就會同時存在多個then回調
this.rejectFunc;
const self = this;
function resolve(value) {
// 使用 setTimeout 實現異步
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'fulfilled';
self.data = value;
// 執行 resolve 函數
self.resolveFuncList.forEach(func => {
func(value)
});
}
})
}
function reject(reason) {
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'rejected';
self.data = value;
self.rejectFunc && self.rejectFunc(reason);
}
})
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
MPromise.prototype.then = function (onFulfilled, onRejected) {
let promise2;
// 區分不一樣狀態下的處理
if (this.status === 'pending') {
return promise2 = new MPromise((res, rej) => {
this.resolveFuncList.push(function (value) {
let x = onFulfilled(value);
resolvePromise(promise2, x, res, rej)
})
this.rejectFunc = function (reason) {
let x = onRejected(reason);
resolvePromise(promise2, x, res, rej)
}
})
}
if (this.status === 'fulfilled') {
return promise2 = new MPromise((res, rej) => {
setTimeout(() => {
let x = onFulfilled(this.data) // 輸出將上一次執行結果
resolvePromise(promise2, x, res, rej)
})
})
}
if (this.status === 'rejected') {
return promise2 = new MPromise((res, rej) => {
setTimeout(() => {
let x = onRejected(this.data)
resolvePromise(promise2, x, res, rej)
})
})
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x instanceof MPromise) {
if (x.status === 'pending') {
x.then(value => {
resolvePromise(promise2, value, resolve, reject)
}, reason => {
reject(reason)
})
} else {
x.then(resolve, reject)
}
} else {
resolve(x)
}
}
複製代碼
有的由於時間有限,會讓手寫 Promise 的 api,如下兩個就經常被問到
實現 Promise.all
/** * Promise.all Promise進行並行處理 * 參數: promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 當這個數組裏的全部promise對象所有進入FulFilled狀態的時候,纔會resolve。 */
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let values = []
let count = 0
promises.forEach((promise, index) => {
promise.then(value => {
console.log('value:', value, 'index:', index)
values[index] = value
count++
if (count === promises.length) {
resolve(values)
}
}, reject)
})
})
}
複製代碼
實現 Promise.rase
/** * Promise.race * 參數: 接收 promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理(取決於哪個更快) */
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve, reject);
});
});
}
複製代碼
promise 的騷操做仍是很是多的,歡迎小夥伴在評論區一塊兒分享大家遇到題(tao)目(lu)。