js面试题之Promise

1、promise

使用promise(MDN)
promise(MDN)
Promises/A+标准
promisejs.org
事件循环

基本输出题

1、new Promise 的参数函数是同步执行
const p = new Promise((resolve, reject) => {
    console.log(1)
    resolve()
    console.log(2)
})
p.then(() => {
    console.log(3)
})
console.log(4)
// 1-> 2 -> 4 ->3

首先new Promise 的参数函数是同步的,所以先输出 1->2;
resolve 后还需要等待进入下一个事件循环。then 把参数函数推入微任务队列,并不直接执行。
输出 4,接着事件循环进入下一轮,输出 3.

2、promise的then函数不同返回值的区别
//给出一个promise
var p = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve(1);
  }, 3000)
})

//下面三种有什么区别?
// 1
p.then(() => {
  return Promise.resolve(2);
}).then((n) => {
  console.log(n)
});

// 2
p.then(() => {
  return 2
}).then((n) => {
  console.log(n)
});

// 3
p.then(2).then((n) => {
  console.log(n)
});

1、输出2。Promise.resolve 就是一个 Promise 对象就相当于返回了一个新的 Promise 对象。然后在下一个事件循环里才会去执行 then
2、输出2。和上一点不一样的是,它不用等下一个事件循环。
3、输出1。then 和 catch 期望接收函数做参数,如果非函数就会发生 Promise 穿透现象,打印的是上一个 Promise 的返回。

3、async await
let a;
const b = new Promise((resolve, reject) => {
  console.log('p1');
  resolve();
}).then(() => {
  console.log('p2');
}).then(() => {
  console.log('p3');
}).then(() => {
  console.log('p4');
});

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');

//p1 -> undefined -> end -> p2 -> p3 -> p4 -> p{pending} -> after1

第一个输出 p1,是因为 Promise 里的方法立即执行。接着调用 resolve,只不过 then 里的方法等下一个周期

第二个输出 undefined,是因为立即执行执行 a 内部的方法,先 console.log(a),但此时的 a 还没赋值给左边的变量,所以只能是 undefined。然后 await b 就得等下一个周期执行了。

第三个输出 end,自然不意外。

接着输出 p2,p3,p4,是因为 await b 等待他执行完了,才轮到 a 内部继续执行。

输出 Promise { pending },事件都进入了循环,a 肯定已经被赋值成了 Promise 对象。所以第二遍 console.log(a),自然就输出这个了。

输出 after1 不奇怪。

await a 时,a 是必须等待 Promise 的状态从 pending 到 fullfilled 才会继续往下执行,可 a 的状态是一直得不到更改的,所以无法执行下面的逻辑。只要在 await a 上面加一行 resolve() 就能让后面的 after 2 得到输出

4、Promise 对象的状态只能被转移一次
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链路中then函数抛出Error对象,也是正常走 then 的链式调用下去
Promise.resolve()
 .then(() => {
   return new Error('error!!!')
 })
 .then((res) => {
   console.log('then: ', res)
 })
 .catch((err) => {
   console.log('catch: ', err)
 })

没有抛出错误和异常,只是 return 了一个对象,即使这个对象是 Error 对象,那自然也是正常走 then 的链式调用下去了,不会触发 catch。

6、promise链路中对错误进行了捕获,后续的函数可能会继续执行
Promise.resolve()
   .then(() => {
       console.log('[onFulfilled_1]');
       throw 'throw on onFulfilled_1';
   })
   .then(() => {
       console.log('[onFulfilled_2]');
   }, err => {     // 捕获错误
       console.log('[onRejected_2]', err);
   })
   .then(() => {   // 该函数将被调用
       console.log('[onFulfilled_3]');
   })
   .catch(err => {
       console.log('[catch]', err);
   });

   //[onFulfilled_1] -> [onRejected_2] throw on onFulfilled_1 -> [onFulfilled_3]
7、事件循环队列的执行顺序
        async function async1() {
            console.log('async1 start')
            await async2()
            console.log('async1 end')
        }

        async function async2() {
            console.log('async2')
        }

        console.log('script start')

        setTimeout(function () {
            console.log('settimeout')
        })

        async1()

        new Promise(function (resolve) {
           console.log('promise1')
            resolve()
        }).then(function () {
           console.log('promise2')

        })

        console.log('script end')

 // script start -> async1 start -> async2 -> promise1 -> script end -> 
 // async1 end -> promise2 -> settimeout

1、执行同步代码,输出 script start;
2、遇到setTimeout,产生一个宏任务
3、然后执行async1函数,async函数还是基于Promise的一些封装,而Promise是属于微任务的一种;因此会把await async2()及后面的所有代码注册微任务。
所以先执行同步代码,输出 async1 start,然后输出 async2

4、继续执行主线程代码同步代码,输出promise1,然后输出 script end;
5、执行主线程微任务,执行第一个微任务,输出 async end;执行第二个微任务,输出promise2;
6、setTimeout属于宏任务,要等待script这个宏任务执行完再执行,所以最后输出script end
参考链接:https://cloud.tencent.com/developer/article/1601176

概念题

1、了解 Promise 吗?
2、Promise 解决的痛点是什么?

1)回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象,是为解决异步操作函数里的嵌套回调(callback hell)问题,代码臃肿,可读性差,只能在回调里处理异常

2)promise可以支持多个并发的请求,获取并发请求中的数据

3)promise可以解决可读性的问题,异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的,这样的代码读起来会非常吃力

4)promise可以解决信任问题,对于回调过早、回调过晚或没有调用和回调次数太少或太多,由于promise只能决议一次,决议值只能有一个,决议之后无法改变,任何then中的回调也只会被调用一次,所以这就保证了Promise可以解决信任问题

3、Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。

1)Promise 解决的痛点还有其他方法可以解决,比如setTimeout、事件监听、回调函数、Generator函数,async/await

2)setTimeout:缺点不精确,只是确保在一定时间后加入到任务队列,并不保证立马执行。只有执行引擎栈中的代码执行完毕,主线程才会去读取任务队列

3)事件监听:任务的执行不取决于代码的顺序,而取决于某个事件是否发生

4)Generator函数虽然将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。即如何实现自动化的流程管理

5)async/await

4、Promise 如何使用?

1)创造一个Promise实例

2)Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

3)可用Promise的try和catch方法预防异常

5、Promise 常用的方法有哪些?它们的作用是什么?

1)Promise.resolve(value)

类方法,该方法返回一个以 value 值解析后的 Promise 对象
如果这个值是个 thenable(即带有 then 方法),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled)
如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回
其他情况以该值为成功状态返回一个 Promise 对象

2)Promise.reject

类方法,且与 resolve 唯一的不同是,返回的 promise 对象的状态为 rejected

3)Promise.prototype.then

实例方法,为 Promise 注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果,then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收

4)Promise.prototype.catch

实例方法,捕获异常,函数形式:fn(err){}, err 是 catch 注册 之前的回调抛出的异常信息

5)Promise.race

类方法,多个 Promise 任务同时执行,返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败

6)Promise.all

类方法,多个 Promise 任务同时执行
如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果

6、Promise 在事件循环中的执行过程是怎样的?

1)事件循环

从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后到消息队列中排队。程序按照代码顺序执行完毕后,查询消息队列中是否有等待的消息。如果有,则按照次序从消息队列中把消息放到执行栈中执行。执行完毕后,再从消息队列中获取消息,再执行,不断重复。由于主线程不断的重复获得消息、执行消息、再取消息、再执行

2)promise的事件循环

Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环

7、Promise 的业界实现都有哪些?

1) promise可以支持多个并发的请求,获取并发请求中的数据

2)promise可以解决可读性的问题,异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的,这样的代码读起来会非常吃力

8、 Promise的问题?解决办法?

promise的问题为:

promise一旦执行,无法中途取消

promise的错误无法在外部被捕捉到,只能在内部进行预判处理

promise的内如何执行,监测起来很难

解决办法

正是因为这些原因,ES7引入了更加灵活多变的async,await来处理异步

怎么取消promise [待完善…]

Promise的then方法接收两个参数:
Promise.prototype.then(onFulfilled, onRejected)
若onFulfilled或onRejected是一个函数,当函数返回一个新Promise对象时,原Promise对象的状态将跟新对象保持一致,详见Promises/A+标准
因此,当新对象保持“pending”状态时,原Promise链将会中止执行。

Promise.resolve().then(() => {
   console.log('1')
   return new Promise(()=>{})  // 返回“pending”状态的Promise对象
}).then(() => {
   // 后续的函数不会被调用
   console.log('2')
}).catch(err => {
   console.log('err', err)
})

顺序输出

给你若干个 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)
]);

1、aysnc await

        async function order(promises) {
            try {
                for (let i = 0; i < promises.length; i++) {
                    let res = await promises[i]
                    console.log(res);
                }
            } catch (error) {
                console.log(error);
            }
        }

2、不使用aysnc await的方法

        function order(promises) {
            let p = Promise.resolve();
            for (let i = 0; i <= promises.length; i++) {
                p = p.then(res => {
                    console.log(res);
                    return promises[i];
                });
            }
        }

并发做异步请求,限制频率

举个例子,有 8 张图片 url,你需要并发去获取它,并且任何时刻同时请求的数量不超过 3 个。也就是说第 4 张图片一定是等前面那一批有一个请求完毕了才能开始,以此类推。

var urls = [
  'https://www.kkkk1000.com/images/1.jpg',
  'https://www.kkkk1000.com/images/2.jpg',
  'https://www.kkkk1000.com/images/3.jpg',
  'https://www.kkkk1000.com/images/4.jpg',
  'https://www.kkkk1000.com/images/5.jpg',
  'https://www.kkkk1000.com/images/6.jpg',
  'https://www.kkkk1000.com/images/7.jpg',
  'https://www.kkkk1000.com/images/8.jpg'
];

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);

解决方案:大概思路就是先将前 limit 个同时进行,然后再通过 then 去控制,每次完成一个后再启动一个

        function limitload(urls, limit) {
            const execNewPromise = () => {
                limit++;
                if (limit < urls.length) {
                    loadImg(urls[limit]).then(() => execNewPromise());
                }
            }

            let promise = Promise.resolve();
            promise.then(() => {
                for (let i = 0; i < limit; i++) {
                    loadImg(urls[i]).then(() => execNewPromise());
                }
            });
        }

手写 Promise

promise实现细节

1、能不能手写一个 Promise 的 polyfill?
2、手写Promise与ajax的结合?
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值