15道ES6 Promise實(shí)戰(zhàn)練習(xí)題,助你快速理解Promise

Promise是ES6中的特性,現(xiàn)在很多前端框架像AngularJS,Vue等在HTTP請(qǐng)求之后都是返回的Promise處理,因此Promise是必須要掌握的一個(gè)知識(shí)點(diǎn)。
本文將為大家分享15道由易到難的ES6 Promise題, 幫助你快速理解Promise。

基礎(chǔ)題

01

const promise = new Promise((resolve, reject) => {
    console.log(1)
    resolve()
    console.log(2)
})
promise.then(() => {
    console.log(3)
})
console.log(4)

解析:
Promise 構(gòu)造函數(shù)是同步執(zhí)行的,promise.then 中的函數(shù)是異步執(zhí)行的。

運(yùn)行結(jié)果:
// => 1
// => 2
// => 4
// => 3
02
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });

}));

first().then((arg) => {
    console.log(arg);
});
console.log(4);

解析:
這道題主要理解js執(zhí)行機(jī)制。
第一輪事件循環(huán),先執(zhí)行宏任務(wù),主script,new Promise立即執(zhí)行,輸出 3,執(zhí)行p這個(gè)new Promise操作,輸出 7,發(fā)現(xiàn)setTimeout,將回調(diào)函數(shù)放入下一輪任務(wù)隊(duì)列(Event Quene),p的then,暫且命名為then1,放入微任務(wù)隊(duì)列,且first也有then,命名為then2,放入微任務(wù)隊(duì)列。執(zhí)行console.log(4),輸出 4,宏任務(wù)執(zhí)行結(jié)束。
再執(zhí)行微任務(wù),執(zhí)行then1,輸出 1,執(zhí)行then2,輸出 2.
第一輪事件循環(huán)結(jié)束,開(kāi)始執(zhí)行第二輪。第二輪事件循環(huán)先執(zhí)行宏任務(wù)里面的,也就是setTimeout的回調(diào),輸出 5.resolve(6)不會(huì)生效,因?yàn)閜的Promise狀態(tài)一旦改變就不會(huì)再變化了。

運(yùn)行結(jié)果:
// => 3
// => 7
// => 4
// => 1
// => 2
// => 5
03
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 有 3 種狀態(tài):pending、fulfilled 或 rejected。狀態(tài)改變只能是 pending->fulfilled 或者 pending->rejected,狀態(tài)一旦改變則不能再變。上面 promise2 并不是 promise1,而是返回的一個(gè)新的 Promise 實(shí)例。

運(yùn)行結(jié)果:
promise1 Promise {<pending>}
promise2 Promise {<pending>}
Uncaught (in promise) Error: error!!!
    at <anonymous>
promise1 Promise {<resolved>: "success"}
promise2 Promise {<rejected>: Error: error!!!
    at <anonymous>}
04
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)
  })

解析:
構(gòu)造函數(shù)中的 resolve 或 reject 只有第一次執(zhí)行有效,多次調(diào)用沒(méi)有任何作用,呼應(yīng)代碼二結(jié)論:promise 狀態(tài)一旦改變則不能再變。

運(yùn)行結(jié)果:
then: success1
05
Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })

解析:
promise 可以鏈?zhǔn)秸{(diào)用。提起鏈?zhǔn)秸{(diào)用我們通常會(huì)想到通過(guò) return this 實(shí)現(xiàn),不過(guò) Promise 并不是這樣實(shí)現(xiàn)的。promise 每次調(diào)用 .then 或者 .catch 都會(huì)返回一個(gè)新的 promise,從而實(shí)現(xiàn)了鏈?zhǔn)秸{(diào)用。

運(yùn)行結(jié)果:
1
2
06
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)
})

解析:
promise 的 .then 或者 .catch 可以被調(diào)用多次,但這里Promise 構(gòu)造函數(shù)只執(zhí)行一次?;蛘哒f(shuō) promise 內(nèi)部狀態(tài)一經(jīng)改變,并且有了一個(gè)值,那么后續(xù)每次調(diào)用 .then 或者 .catch 都會(huì)直接拿到該值。

運(yùn)行結(jié)果:
once
success 1005
success 1007
07
Promise.resolve()
  .then(() => {
    // return Promise.reject(new Error('error!!!'))
    // throw new Error('error!!!')
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

解析:

  • .then 或者 .catch 中 return 一個(gè) error 對(duì)象并不會(huì)拋出錯(cuò)誤,所以不會(huì)被后續(xù)的 .catch 捕獲,
  • .then方法中需要改成其中一種:return Promise.reject(new Error('error!!!'))或 throw new Error('error!!!')
  • 因?yàn)榉祷厝我庖粋€(gè)非 promise 的值都會(huì)被包裹成 promise 對(duì)象,即 return new Error('error!!!') 等價(jià)于 return Promise.resolve(new Error('error!!!'))。
運(yùn)行結(jié)果:
then:  Error: error!!!
    at <anonymous>
08
const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)

解析:.then 或 .catch 返回的值不能是 promise 本身,否則會(huì)造成死循環(huán)。類似于:

process.nextTick(function tick () {
  console.log('tick')
  process.nextTick(tick)
})
運(yùn)行結(jié)果:
TypeError: Chaining cycle detected for promise #<Promise>
09
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

解析:.then 或者 .catch 的參數(shù)期望是函數(shù),傳入非函數(shù)則會(huì)發(fā)生值穿透。

運(yùn)行結(jié)果:1
10
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)
  })

解析:

  • .then 可以接收兩個(gè)參數(shù),第一個(gè)是處理成功的函數(shù),第二個(gè)是處理錯(cuò)誤的函數(shù)。
  • .catch 是 .then 第二個(gè)參數(shù)的簡(jiǎn)便寫(xiě)法,但是它們用法上有一點(diǎn)需要注意:.then 的第二個(gè)處理錯(cuò)誤的函數(shù)捕獲不了第一個(gè)處理成功的函數(shù)拋出的錯(cuò)誤,而后續(xù)的 .catch 可以捕獲之前的錯(cuò)誤。
  • catch(rejectionFn) 其實(shí)就是 then(null, rejectionFn) 的別名。
當(dāng)然以下代碼也可以:
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)
  })
運(yùn)行結(jié)果:
fail2:  Error: error
    at success (<anonymous>)
new Promise((resolve, reject) => {
  throw new Error('errrr')
})
  .then(function success (res) {
    console.error('success: ', res)
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })
運(yùn)行結(jié)果:
fail1:  Error: errrr
    at <anonymous>:2:9
    at new Promise (<anonymous>)
    at <anonymous>:1:1
11
process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')

解析:
process.nextTick 和 promise.then 都屬于 microtask,而 setImmediate 屬于 macrotask,在事件循環(huán)的 check 階段執(zhí)行。事件循環(huán)的每個(gè)階段(macrotask)之間都會(huì)執(zhí)行 microtask,事件循環(huán)的開(kāi)始會(huì)先執(zhí)行一次 microtask。

運(yùn)行結(jié)果:
end
nextTick
then
setImmediate

編程題
上面題目太基礎(chǔ),沒(méi)有挑戰(zhàn)性?那就來(lái)點(diǎn)有難度的!

12

紅燈3秒亮一次,綠燈1秒亮一次,黃燈2秒亮一次;如何使用Promise讓三個(gè)燈不斷交替重復(fù)亮燈?(??低暪P試題)

function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}

分析:
先看題目,題目要求紅燈亮過(guò)后,綠燈才能亮,綠燈亮過(guò)后,黃燈才能亮,黃燈亮過(guò)后,紅燈才能亮……所以怎么通過(guò)Promise實(shí)現(xiàn)?
換句話說(shuō),就是紅燈亮起時(shí),承諾2s秒后亮綠燈,綠燈亮起時(shí)承諾1s后亮黃燈,黃燈亮起時(shí),承諾3s后亮紅燈……這顯然是一個(gè)Promise鏈?zhǔn)秸{(diào)用,看到這里你心里或許就有思路了,我們需要將我們的每一個(gè)亮燈動(dòng)作寫(xiě)在then()方法中,同時(shí)返回一個(gè)新的Promise,并將其狀態(tài)由pending設(shè)置為fulfilled,允許下一盞燈亮起。

function red() {
  console.log('red');
}
function green() {
  console.log('green');
}
function yellow() {
  console.log('yellow');
}
let myLight = (timer, cb) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      cb();
      resolve();
    }, timer);
  });
};
let myStep = () => {
  Promise.resolve().then(() => {
    return myLight(3000, red);
  }).then(() => {
    return myLight(2000, green);
  }).then(()=>{
    return myLight(1000, yellow);
  }).then(()=>{
    myStep();
  })
};
myStep();
// output:
// => red
// => green
// => yellow
13

請(qǐng)實(shí)現(xiàn)一個(gè)mergePromise函數(shù),把傳進(jìn)去的數(shù)組按順序先后執(zhí)行,并且把返回的數(shù)據(jù)先后放到數(shù)組data中。

const timeout = ms => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, ms);
});

const ajax1 = () => timeout(2000).then(() => {
    console.log('1');
    return 1;
});

const ajax2 = () => timeout(1000).then(() => {
    console.log('2');
    return 2;
});

const ajax3 = () => timeout(2000).then(() => {
    console.log('3');
    return 3;
});

const mergePromise = ajaxArray => {
    // 在這里實(shí)現(xiàn)你的代碼

};

mergePromise([ajax1, ajax2, ajax3]).then(data => {
    console.log('done');
    console.log(data); // data 為 [1, 2, 3]
});
// 要求分別輸出
// 1
// 2
// 3
// done
// [1, 2, 3]

分析:這道題主要考察用Promise控制異步流程,首先ajax1,ajax2,ajax3都是函數(shù),只是這些函數(shù)執(zhí)行后會(huì)返回一個(gè)Promise,按照題目要求只要順序執(zhí)行這三個(gè)函數(shù)就好了,然后把結(jié)果放到data中;

答案:
const mergePromise = ajaxArray => {
  // 在這里實(shí)現(xiàn)你的代碼

  // 保存數(shù)組中的函數(shù)執(zhí)行后的結(jié)果
  var data = [];

  // Promise.resolve方法調(diào)用時(shí)不帶參數(shù),直接返回一個(gè)resolved狀態(tài)的 Promise 對(duì)象。
  var sequence = Promise.resolve();

  ajaxArray.forEach(item => {
    // 第一次的 then 方法用來(lái)執(zhí)行數(shù)組中的每個(gè)函數(shù),
    // 第二次的 then 方法接受數(shù)組中的函數(shù)執(zhí)行后返回的結(jié)果,
    // 并把結(jié)果添加到 data 中,然后把 data 返回。
    sequence = sequence.then(item).then(res => {
      data.push(res);
      return data;
    });
  });

// 遍歷結(jié)束后,返回一個(gè) Promise,也就是 sequence, 他的 [[PromiseValue]] 值就是 data,
// 而 data(保存數(shù)組中的函數(shù)執(zhí)行后的結(jié)果) 也會(huì)作為參數(shù),傳入下次調(diào)用的 then 方法中。
  return sequence;
};

大概思路如下:

  • 全局定義一個(gè)promise實(shí)例sequence,循環(huán)遍歷函數(shù)數(shù)組,
  • 每次循環(huán)更新sequence,將要執(zhí)行的函數(shù)item通過(guò)sequence的then方法進(jìn)行串聯(lián),
  • 并且將執(zhí)行結(jié)果推入data數(shù)組,最后將更新的data返回,
  • 這樣保證后面sequence調(diào)用then方法,如何后面的函數(shù)需要使用data只需要將函數(shù)改為帶參數(shù)的函數(shù)。
14

現(xiàn)有8個(gè)圖片資源的url,已經(jīng)存儲(chǔ)在數(shù)組urls中,且已有一個(gè)函數(shù)function loading,輸入一個(gè)url鏈接,返回一個(gè)Promise,該P(yáng)romise在圖片下載完成的時(shí)候resolve,下載失敗則reject。

要求:任何時(shí)刻同時(shí)下載的鏈接數(shù)量不可以超過(guò)3個(gè)。
請(qǐng)寫(xiě)一段代碼實(shí)現(xiàn)這個(gè)需求,要求盡可能快速地將所有圖片下載完成。
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('一張圖片加載完成');
            resolve();
        }
        img.onerror = reject;
        img.src = url;
    })
};

解析

  1. 題目的意思是需要先并發(fā)請(qǐng)求3張圖片,當(dāng)一張圖片加載完成后,又會(huì)繼續(xù)發(fā)起一張圖片的請(qǐng)求,讓并發(fā)數(shù)保持在3個(gè),直到需要加載的圖片都全部發(fā)起請(qǐng)求。
  2. 用Promise來(lái)實(shí)現(xiàn)就是,先并發(fā)請(qǐng)求3個(gè)圖片資源,這樣可以得到3個(gè)Promise,組成一個(gè)數(shù)組promises,然后不斷調(diào)用Promise.race來(lái)返回最快改變狀態(tài)的Promise,然后從數(shù)組promises中刪掉這個(gè)Promise對(duì)象,再加入一個(gè)新的Promise,直到全部的url被取完
  3. 最后再使用Promise.all來(lái)處理一遍數(shù)組promises中沒(méi)有改變狀態(tài)的Promise。
function limitLoad(urls, handler, limit) {
  // 對(duì)數(shù)組做一個(gè)拷貝
    const sequence = […urls];

  let promises = [];
  //并發(fā)請(qǐng)求到最大數(shù)
  promises = sequence.splice(0, limit).map((url, index) => {
    // 這里返回的 index 是任務(wù)在 promises 的腳標(biāo),用于在 Promise.race 之后找到完成的任務(wù)腳標(biāo)
    return handler(url).then(() => {
      return index;
    });
  });

  // 利用數(shù)組的 reduce 方法來(lái)以隊(duì)列的形式執(zhí)行
  return sequence.reduce((last, url, currentIndex) => {
    return last.then(() => {
      // 返回最快改變狀態(tài)的 Promise
      return Promise.race(promises)
    }).catch(err => {
      // 這里的 catch 不僅用來(lái)捕獲前面 then 方法拋出的錯(cuò)誤
      // 更重要的是防止中斷整個(gè)鏈?zhǔn)秸{(diào)用
      console.error(err)
    }).then((res) => {
      // 用新的 Promise 替換掉最快改變狀態(tài)的 Promise
      promises[res] = handler(sequence[currentIndex]).then(() => {
        return res
      });
    })
  }, Promise.resolve()).then(() => {
    return Promise.all(promises)
  })

}

limitLoad(urls, loadImg, 3);

/*
因?yàn)?limitLoad 函數(shù)也返回一個(gè) Promise,所以當(dāng) 所有圖片加載完成后,可以繼續(xù)鏈?zhǔn)秸{(diào)用

limitLoad(urls, loadImg, 3).then(() => {
    console.log('所有圖片加載完成');
}).catch(err => {
    console.error(err);
})
*/
15 封裝一個(gè)異步加載圖片的方法
解析:
function loadImageAsync(url) {
    return new Promise(function(resolve,reject) {
        var image = new Image();
        image.onload = function() {
            resolve(image) 
        };
        image.onerror = function() {
            reject(new Error('Could not load image at' + url));
        };
        image.src = url;
     });
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容