上期讲了promise
基本概念和用法,今天结合上期的内容,讲解几道经典的相关面试题。javascript
Promise
构造函数会当即执行,而Promise.then()
内部的代码在当次事件循环的结尾当即执行(微任务)。promise
的状态一旦由等待pending
变为成功fulfilled
或者失败rejected
。那么当前promise
被标记为完成,后面则不会再次改变该状态。resolve
函数和reject
函数都将当前Promise
状态改成完成,并将异步结果,或者错误结果当作参数返回。Promise.resolve(value)
返回一个状态由给定 value 决定的 Promise 对象。若是该值是 thenable(即,带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行决定;不然的话(该 value 为空,基本类型或者不带 then 方法的对象),返回的 Promise 对象状态为 fulfilled,而且将该 value 传递给对应的 then 方法。一般而言,若是你不知道一个值是不是 Promise 对象,使用 Promise.resolve(value) 来返回一个 Promise 对象,这样就能将该 value 以 Promise 对象形式使用。html
Promise.all(iterable)/Promise.race(iterable)
简单理解,这2个函数,是将接收到的promise
列表的结果返回,区别是,all
是等待全部的promise
都触发成功了,才会返回,而arce
有一个成功了就会返回结果。其中任何一个promise
执行失败了,都会直接返回失败的结果。前端
promise
对象的构造函数只会调用一次,then
方法和catch
方法都能屡次调用,但一旦有了肯定的结果,再次调用就会直接返回结果。const promise = new Promise((resolve, reject) => {
console.log(1); resolve(); console.log(2); reject('error'); }) promise.then(() => { console.log(3); }).catch(e => console.log(e)) console.log(4); 复制代码
能够看:规则一,promise
构造函数的代码会当即执行,then
或者reject
里面的代码会放入异步微任务队列,在宏任务结束后会当即执行。规则二:promise
的状态一旦变动为成功或者失败,则不会再次改变,因此执行结果为:1,2,4,3。而catch
里面的函数不会再执行。html5
const promise = new Promise((resolve, reject) => {
setTimeout(() => { console.log('once') resolve('success') }, 1000) }) promise.then((res) => { console.log(res) }) promise.then((res) => { console.log(res) }) 复制代码
根据规则6,promise
的构造函数只会执行一次,而then
方法能够屡次调用,可是第二次是直接返回结果,不会有异步等待的时间,因此执行结果是: 过一秒打印:once,success,success
。java
在浏览器上,下面的程序会一次输出哪些内容?node
const p1 = () => (new Promise((resolve, reject) => {
console.log(1); let p2 = new Promise((resolve, reject) => { console.log(2); const timeOut1 = setTimeout(() => { console.log(3); resolve(4); }, 0) resolve(5); }); resolve(6); p2.then((arg) => { console.log(arg); }); })); const timeOut2 = setTimeout(() => { console.log(8); const p3 = new Promise(reject => { reject(9); }).then(res => { console.log(res) }) }, 0) p1().then((arg) => { console.log(arg); }); console.log(10); 复制代码
事件循环:javascript
的执行规则里面有个事件循环Event Loot的规则,在事件循环中,异步事件会放到异步队列里面,可是异步队列里面又分为宏任务和微任务,浏览器端的宏任务通常有:script标签,setTimeout,setInterval,setImmediate,requestAnimationFrame
。微任务有:MutationObserver,Promise.then catch finally
。宏任务会阻塞浏览器的渲染进程,微任务会在宏任务结束后当即执行,在渲染以前。web
回到题目,结果为:'1,2,10,5,6,8,9,3'。你答对了吗?若是对了,那你基本理解了事件队列,微任务,宏任务了。面试
第一步:执行宏任务,结合规则一,输出:1,2,10。这时候事件循环里面有异步任务timeOut1,timeOut2,p2.then,p1.then
。chrome
第二步:宏任务执行完后Event Loop
会去任务队列取异步任务,微任务会优先执行,这时候会前后执行p2.then,p1.then
,打印5,6。promise
第三步:微任务执行完了,开始宏任务,因为2个settimeout
等待时间同样,因此会执行先进入异步队列的timeOut2,前后打印:8。执行宏任务的过程当中,p3.then微任务进入了队列,宏任务执行完毕会执行微任务,输出:9。以后执行timeOut1,输出:3。
第四步:结合规则6,因为p2这个Promise
对象的执行结果已经肯定,因此4不会被打印。
注:在node.js
上输出结果并非这样的,由于node.js
的事件循环跟浏览器端的有区别。
async/await
的状况下,顺序执行一组异步代码函数,并输出最后的结果。在上篇文章中,已经讲到过,利用promise.resolve
结合reduce
能顺序执行一组异步函数。
const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...dd) => x => dd.reduce(applyAsync, Promise.resolve(x)); const transformData = composeAsync(funca, funcb, funcc, funcd); transformData(1).then(result => console.log(result,'last result')).catch(e => console.log(e)); 复制代码
以上代码能够封装成工具来使用,利用的是规则4,promise.resolve
函数的特色,其中dd
能够是一组同步函数,也能够是异步函数。最后的结果在result
里面,异常信息能在最后捕获。想看更具体的能够查看这篇文章: promise讲解
顺序加载10张图片,图片地址已知,可是同时最多加载3张图片,要求用promise
实现。
const baseUrl = 'http://img.aizhifou.cn/';
const urls = ['1.png', '2.png', '3.png', '4.png', '5.png','6.png', '7.png', '8.png', '9.png', '10.png']; const loadImg = function (url, i) { return new Promise((resolve, reject) => { try { // 加载一张图片 let image = new Image(); image.onload = function () { resolve(i) } image.onerror = function () { reject(i) }; image.src = baseUrl + url; } catch (e) { reject(i) } }) } function startLoadImage(urls, limits, endHandle) { // 当前存在的promise队列 let promiseMap = {}; // 当前索引对应的加载状态,不管成功,失败都会标记为true,格式: {0: true, 1: true, 2: true...} let loadIndexMap = {}; // 当前以及加载到的索引,方便找到下一个未加载的索引,为了节省性能,其实能够不要 let loadIndex = 0; const loadAImage = function () { // 全部的资源都进入了异步队列 if (Object.keys(loadIndexMap).length === urls.length) { // 全部的资源都加载完毕,或者进入加载状态,递归结束 const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, []) Promise.all(promiseList).then(res => { // 这里若是没有加载失败,就会在全部加载完毕后执行,若是其中某个错误了,这里的结果就不许确,不过这个不是题目要求的。 console.log('all'); endHandle && endHandle() }).catch((e) => { console.log('end:' + e); }) } else { // 遍历,知道里面有3个promise while (Object.keys(promiseMap).length < limits) { for (let i = loadIndex; i < urls.length; i++) { if (loadIndexMap[i] === undefined) { loadIndexMap[i] = false; promiseMap[i] = loadImg(urls[i], i); loadIndex = i; break; } } } // 获取当前正在进行的promise列表,利用reduce从promiseMap里面获取 const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, []) Promise.race(promiseList).then((index) => { // 其中一张加载成功,删除当前promise,让PromiseList小于limit,开始递归,加载下一张 console.log('end:' + index); loadIndexMap[index] = true; delete promiseMap[index]; loadAImage(); }).catch(e => { // 加载失败也继续 console.log('end:' + e); loadIndexMap[e] = true; delete promiseMap[e]; loadAImage(); }) } } loadAImage() } startLoadImage(urls, 3) 复制代码
将代码复制到chrome浏览器能够看到下面的运行结果: 能够看到,全部图片加载完成,在没有失败的状况下,打印出来
all
。
解析:根据规则5,Promise.race
方法接受的参数中有一个promise
对象返回结果了就会当即触发成功或者失败的函数。这里利用这个特性,先将promise
队列循环加入,直到达到限制,等待race
,race
后又加入一个promise
,利用递归一直循环这个过程,到最后用promise.all
捕获剩下的图片加载。
写出下面函数的执行结果:
Promise.resolve(1)
.then(2) .then(Promise.resolve(3)) .then(console.log) 复制代码
根据规则4,Promise.resolve(1)
会返回一个promise对象
而且会将1当作then
的参数。而.then 或者 .catch 的参数指望是函数,传入非函数则会发生值穿透。因此最后会输出:1。
如何取消一个promise
? 刚开始拿到这个题会以为比较蒙,实际上,咱们能够用Promise,race
的特色,多个Promise
有个状态变为完成,就会立马返回。
function wrap(p) {
let obj = {}; let p1 = new Promise((resolve, reject) => { obj.resolve = resolve; obj.reject = reject; }); obj.promise = Promise.race([p1, p]); return obj; } let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 1000); }); let obj = wrap(promise); obj.promise.then(res => { console.log(res); }); // obj.resolve("请求被拦截了"); 复制代码
一旦开发者在1秒内主动调用obj.resolve
,那么obj.promise
方法就会被替换成咱们本身的方法,而不会执行let promise
的then
方法,实现上比较巧妙。
promise
对象在JavaScript
中的使用相对复杂,由于写法多变,并且灵活,提供的方法又比较复杂难懂,在ES6普及的今天,使用范围也广,因此会高频的出如今面试过程当中。
相关阅读:
知道html5 Web Worker标准吗?能实现JavaScript的多线程?
学习如逆水行舟,不进则退,前端技术飞速发展,若是天天不坚持学习,就会跟不上,我会陪着你们,天天坚持推送博文,跟你们一同进步,但愿你们能关注我,第一时间收到最新文章。
我的公众号: