Promise 精选部分面试题初探

2020-10-30  本文已影响0人  酷酷的凯先生

# 前言

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 以及后续的维护将会是一个很痛苦的过程。

回调地狱的特点大概有以下几个:

  1. 代码臃肿。
  2. 可读性差。
  3. 耦合度过高,可维护性差。
  4. 代码复用性差。
  5. 容易滋生 bug。
  6. 只能在回调里处理异常。

能不能用一种更加友好的代码组织方式,解决异步嵌套的问题 ?
答案是肯定的,如下是最初提出的设想:

let 请求结果1 = 请求1();
let 请求结果2 = 请求2(请求结果1); 
let 请求结果3 = 请求3(请求结果2); 
let 请求结果4 = 请求2(请求结果3); 
let 请求结果5 = 请求3(请求结果4); 

类似上面这种同步的写法,于是 Promise 规范诞生了,并且在业界有了很多实现来解决 回调地狱 的痛点。比如业界著名的 Qbluebirdbluebird 甚至号称运行最快的类库。

Promise 规范诞生后最初的设想得以实现,如下代码所示:

new Promise(请求1)
    .then(请求2(请求结果1))
    .then(请求3(请求结果2))
    .then(请求4(请求结果3))
    .then(请求5(请求结果4))
    .catch(处理异常(异常信息))

# Promise 常用的API

一个 Promise 对象有三个状态:

并且状态一旦改变,便不能再被更改为其他状态,有且只有两种情况,如下:

  1. pending ==> resolved
  2. 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 一般使用过程

  1. 首先初始化一个 Promise 对象,可以通过两种方式创建, 这两种方式都会返回一个 Promise 对象。
    (1)、new Promise(fn)
    (2)、Promise.resolve(fn)

  2. 然后调用上一步返回的 promise 对象的 then 方法,注册回调函数。

  3. then 中的回调函数可以有一个参数,也可以不带参数。如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数。比如:

new Promise(fn).then(fn1(value){
  //处理value
})
  1. 最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常。

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

# 小试牛刀

读到这里,看下面记到面试题你能回答出来几道?

1、了解 Promise 吗?
2、Promise 解决的痛点是什么?
3、Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。
4、Promise 如何使用?
5、Promise 常用的方法有哪些?它们的作用是什么?
6、Promise 在事件循环中的执行过程是怎样的?
7、Promise 的业界实现都有哪些?

上一篇下一篇

猜你喜欢

热点阅读