Promise 精选部分面试题初探
# 前言
Promise
: 英文意思是 承诺、许诺。
即先承诺后兑现,先发送请求,过段时间在给你数据,而这个时间可长可短。
综上所述:
Promise
是一个异步编程的一种解决方案
,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 纳入进规范中。
# Priomise 的由来
在 Promise 出现以前,我们处理一个异步网络请求,大概是这样:
// 请求:代表 一个异步网络调用。
// 请求结果:代表网络请求的响应。
请求1(function(请求结果1){
处理请求结果1
})
需要根据第一个网络请求的结果,再去执行第二个网络请求,代码大概如下:
请求1(function(请求结果1){
请求2(function(请求结果2){
处理请求结果2
})
})
如需求是永无止境的,于是乎出现了如下的代码:
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
请求6(function(请求结果3){
...
})
})
})
})
})
})
是不是脑瓜子嗡嗡的~~~ 这就是臭名昭著的 回调地狱
现身了。
再加上我们基本上还要对每次请求的结果进行一些逻辑处理,代码会更加臃肿,以后代码 review 以及后续的维护将会是一个很痛苦的过程。
回调地狱的特点大概有以下几个:
- 代码臃肿。
- 可读性差。
- 耦合度过高,可维护性差。
- 代码复用性差。
- 容易滋生 bug。
- 只能在回调里处理异常。
能不能用一种更加友好的代码组织方式,解决异步嵌套的问题 ?
答案是肯定的,如下是最初提出的设想:
let 请求结果1 = 请求1();
let 请求结果2 = 请求2(请求结果1);
let 请求结果3 = 请求3(请求结果2);
let 请求结果4 = 请求2(请求结果3);
let 请求结果5 = 请求3(请求结果4);
类似上面这种同步的写法,于是 Promise
规范诞生了,并且在业界有了很多实现来解决 回调地狱
的痛点。比如业界著名的 Q
和 bluebird
,bluebird
甚至号称运行最快的类库。
Promise
规范诞生后最初的设想得以实现,如下代码所示:
new Promise(请求1)
.then(请求2(请求结果1))
.then(请求3(请求结果2))
.then(请求4(请求结果3))
.then(请求5(请求结果4))
.catch(处理异常(异常信息))
# Promise 常用的API
一个 Promise 对象有三个状态:
-
pending
,异步任务正在进行。 -
resolved
, 异步任务执行成功。 -
rejected
,异步任务执行失败。
并且状态一旦改变,便不能再被更改为其他状态,有且只有两种情况,如下:
- pending ==> resolved
- pending ==> rejected
1. Promise.resolve(value)
类方法,该方法返回一个以 value 值解析后的 Promise 对象
(1)、如果这个值是个 thenable(即带有 then 方法),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled)
(2)、如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。
(3)、其他情况以该值为成功状态返回一个 Promise 对象。
举个简单的小栗子:
// 如果传入的 value 本身就是 Promise 对象,
// 则该对象作为 Promise.resolve 方法的返回值返回
function fn(resolve){
setTimeout(function(){
resolve(1);
},1000);
}
let p0 = new Promise(fn);
let p1 = Promise.resolve(p0);
console.log(p0 === p1); // true 返回的 Promise 即是 入参的 Promise 对象。
传入 thenable
对象,返回 Promise 对象跟随 thenable 对象的最终状态。
ES6 Promises 里提到了 Thenable 这个概念,简单来说它就是一个非常类似 Promise 的东西。最简单的例子就是 jQuery.ajax,它的返回值就是 thenable 对象。但是要谨记,并不是只要实现了 then 方法就一定能作为 Promise 对象来使用。
// 如果传入的 value 本身就是 thenable 对象,
// 返回的 promise 对象会跟随 thenable 对象的状态。
let p = Promise.resolve($.ajax('/test/test.json')); // => promise对象
p.then(function(value){
console.log(value);
});
返回一个状态已变成 resolved 的 Promise 对象。
let p = Promise.resolve(1);
console.log(p); // 是一个状态置为resolved的Promise对象
2. Promise.reject(value)
类方法,且与 resolve
唯一的不同是,返回的 promise 对象的状态为 rejected
。
3. Promise.all( [ 多个 promise 组成的数组 ] )
类方法,多个 Promise 任务同时执行。
如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。
如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。
4. Promise.race( [ 多个 promise 组成的数组 ] )
类方法,多个 Promise 任务同时执行,
返回最先执行结束的 Promise 任务的结果,不管这个结果是成功还是失败。
5. Promise.prototype.then
实例方法,为 Promise 注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果
then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。
# Promise 一般使用过程
-
首先初始化一个 Promise 对象,可以通过两种方式创建, 这两种方式都会返回一个 Promise 对象。
(1)、new Promise(fn)
(2)、Promise.resolve(fn) -
然后调用上一步返回的 promise 对象的 then 方法,注册回调函数。
-
then 中的回调函数可以有一个参数,也可以不带参数。如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数。比如:
new Promise(fn).then(fn1(value){
//处理value
})
- 最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常。
总结:Promise 在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环。
# 小试牛刀
读到这里,看下面记到面试题你能回答出来几道?
1、了解 Promise 吗?
2、Promise 解决的痛点是什么?
3、Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。
4、Promise 如何使用?
5、Promise 常用的方法有哪些?它们的作用是什么?
6、Promise 在事件循环中的执行过程是怎样的?
7、Promise 的业界实现都有哪些?