promise 以及经典面试题

Posted All rivers run in to th

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了promise 以及经典面试题相关的知识,希望对你有一定的参考价值。

1.Promise 它是一个ES6提出一个新语法,用来优化异步代码的写法。promise:承诺

● 生活中,它是用来表述 对将来要发生的事情的肯定。 例如 : 高中生说,老师,我会考上一所好大学的;销售员说,老板,我一定会签下大单的;程序员说,老妈,我过年一定会带个女朋友回来的。
● 在ES6中,它是新增的构造器(Array, Object, Promise),用来优化实现异步操作。在没有它之前,javascript中的异步处理,大多是利用回调函数来实现的。典型的几种如下:(1)setTimeout (2)ajax(3)nodejs中的文件读取。现在有了promise,就可以对这些异步操作进行改写了。

主要学习内容

● 构造器
● 三种状态和值
● 经典调用格式
● then的执行逻辑和返回值
● then链式用法($().x().x())
● async await

2.Promise的基本使用

Promise是承诺的意思,es6中提出一个新api。
用来优化异步代码。它的经典使用方式如下

var p1 = new Promise(function(resolve,reject)
     //异步操作 resolve(obj1)  或者 reject(obj2)
);
p1.then(function(rs)
    // 如果p1的状态是resolved,则then中的函数
    //会执行,且obj1的值会传给rs
).catch(function(rs)
    // 如果p1的状态是reject,则catch中的函数
    //    会执行,且obj2的值会传给rs
).finally(function()
    // 一定会执行的函数
)

构造器

// const obj = new Object()
// const arr = new Array()
const p1 = new Promise(function(resolve,reject)
  // 执行异步代码
  // 调用resolve,或者reject
);
console.dir(p1)

要点:

● 构造器必须要给定一个参数,如果不给就是会报错。例如,new Promise() 报错的信息是: Promise resolver undefined is not a function
● 构造器的实参是一个函数,这个函数的特殊之处在于它有两个形参(resolve,reject),这两个形参也是函数。在格式上,也可以采用箭头函数来改写。例如:var p1 = new Promise((resolve,reject)=>)。
● 在函数体的内部, 一般会执行异步代码,然后根据情况来调用resolve()或者是reject() 。调用resolve或者是reject后会产生什么样的后果,在后面小节介绍。 当然了,再次强调一下resolve和reject只是形参名,可以改写成其它的。

三种状态和值

一个Promise对象的状态可能是如下三种之一:pending,resolved,rejected 。下面分别介绍。

初始态pending

pending。它的意思是 “待定的,将发生的”,相当于是一个初始状态。 创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。

var p = new Promise((resolve,rejectok,err)=> console.info('发呆.....' ))
console.dir(p)

这个初始状态就是你对别人说出承诺的那个瞬间,此时,大家都不知道这个承诺是否能兑现。

成功态resolved

也有叫fulfilled的。

resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法(即第一个参数)。

var p = new Promise((resolve,reject)=> resolved();)
console.dir(p)

注意,上面的resolve和reject仅是形参名而已。

失败态rejected

rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。

var p = new Promise((resolve,reject)=> reject() )
console.dir(p)

三种状态小结

  1. 状态是可转化。

最初创建promise对象时,默认状态是pending,如果在函数体内部调用了第一个参数对应的函数,则状态变成了resolved;如果调用了第二个参数对应的函数,则状态变成了rejected。

 pending -----  resolve() --> resolved;
 pending -----  reject()  --> rejected;
  1. 状态转换是不可逆的。

一旦从pending —> resolved(或者是rejected),就不可能再回到pending,也不能由resolved变成rejected。

promise的值

一个promise对象除了状态之外,还有promiseValue。在构造器中,这个值在调用resolve和reject方法时传入。

例如:

var p = new Promise( (resolve,reject) =>  resolve(123);  );
//  此时,prommise对象p的状态是 resolved,值是123。
console.dir(p)
var p = new Promise( (resolve,reject) =>  reject(123);  );
//  此时,prommise对象p的状态是 rejected,值是123
console.dir(p)

promise的兑现

在构造器的函数体中,一旦状态发生了变化,就会进行then,或者是catch中去,同时把promiseValue传入对应的函数。

具体来说:

● 状态从pending变成resolved,进入then中,调用函数,并传入此时的promiseValue(就是调用resolve时传入的实参)
● 状态从pending变成rejected,进入catch中,调用函数,并传入此时的promiseValue(就是调用reject时传入的实参)

根据实际情况的需要,也可以不加上finally()。

小结

构造器

三种状态和值

经典格式

使用promise改造回调函数

下面我们回到具体的问题:如何对现有的,通过回调函数来实现异步操作的代码进行改写。

基本套路

第一步:建立模板。这里的模板是指固定的套路:写一个空函数,在函数体中的创建一个promise对象,并返回。

function fnName()
    var p = new Promise((resolve,reject)=>  )
    return p;

当然, 如果使用箭头函数的话,还可以简化。
第二步:把异步功能写入构造器中,根据实际来决定是否调用resolve,reject。

function fnName()
    var p = new Promise((resolve,reject)=>
        // 这里写具体的代码,并在某个恰当的时机去调用resolve和reject函数。
    )
    return p;

 

第三步:调用函数。
通过fnName().then().catch() 结构来调用这个函数。

示例1: ajax的改写

原回调函数的写法

function getData(url,success) 
            const xhr = new XMLHttpRequest()
            xhr.open('get', url)
            xhr.onreadystatechange = function ()
                if(this.readyState === 4)
                    // 加载完成
                    const d = JSON.parse(xhr.responseText);
                    success(d)
                
            
            xhr.send()
        

改写之后

function getDataPromise (url) 
            return new Promise((resolve,reject) => 
                const xhr = new XMLHttpRequest()
                xhr.open('get', url)
                xhr.onreadystatechange = function ()
                    if(this.readyState === 4)
                        // 加载完成
                        const d = JSON.parse(xhr.responseText);
                        resolve(d)
                    
                
                xhr.send()
            )
        

then的格式及执行逻辑

目标

掌握then方法的使用;

作用

then方法的作用是为Promise对象添加状态改变时的回调函数。

下面从其调用格式,执行逻辑及返回值三个方面来介绍

then的格式

它有两个参数,每个参数都是函数。 第二个参数是可选的。

如下:

// p 是一个promise对象
p.then(函数1[,函数2])

它的两个参数都是函数。

● 第一个参数是resolved状态的回调函数。当p的状态从pending变成了resolved,函数1会执行。
● 第二个参数是rejected状态的回调函数。 当p的状态从pending变成了rejected,函数2会执行。

其中第二个参数是可选的,如果只写一个参数的话就是如下:

promise对象.then(函数1)

执行逻辑

以如下代码为例

var p = new Promise((resolve,reject)=>
   //  resolve(val1);
   reject(val2)
)

p.then(
  	okVal=>
    	console.info("成功");
    	console.log(okValue);
		, 
  	errVal=>
    	console.info("失败");
    	console.log(errValue);
    
)

它的两个参数都是函数,其执行逻辑是:

● 如果promise对象的状态是resolved,则then()会执行第一个函数,并传入当前的PromiseValue(即上面的val1);
● 如果promise对象的状态是rejected,则then()会执行第二个函数,并传入当前的PromiseValue(即上面的val2);
● 特别地,如果promise对象的状态是rejected,且此时then方法并没有设置第二个参数,就会向外抛出一个错误,错误的提示大约是Uncaught (in promise)。

示例代码1

var p = new Promise((resolve,reject)=>
	resolve(1)//主动调用resolve,并传入
)
// 此时,P的状态是resolved,且值promiseValue 是1.
p.then((res)=>
    // 因为p的状态是resolved,所以自动执行then的第一个参数,并且把promisevalue传进来。
	console.log("then,ok",res)
)

执行输出结果是:then,ok,1

示例代码2

var p = new Promise((resolve,reject)=>
	reject(2)//主动调用reject,并传入实参
)
// 此时,P的状态是rejected,且值promiseValue 是2.

p.then((res)=>
    // 因为p的状态是resolved,所以这句代码不会执行。
	console.log("then,ok",res)
,(err)=>
    // 因为p的状态是rejected,所以自动执行then的第二个参数,并且把promisevalue传进来。
	console.log("then,err",err)
)

执行输出结果是:then,err,2

then的返回值(难点)

then()方法的返回值也是一个promise对象,所以它支持链式写法。但是要注意的是它的返回值是一个新的promise对象,与调用then方法的并不是同一个对象

看下如下代码:

var p1 = new Promise(()=>);
var p2 = p1.then(function f_ok(), function f_err()); 
// p2也是一个promise对象。

console.log(p1 === p2); // false

如上代码可以说明p1.then()的结果是一个与p1不同的promise对象。换句话说,then()会封装一个全新的promise对象p2。那既然 p2也是一个promise对象,那么,p2的状态(promiseStatus)和值(promiseValue)分别是什么?

p2的状态及promiseValue如何确定?

p2的状态及promiseValue按如下规则来确定

● 如果p1的状态是pending,则p2的状态也是pending。
● 如果p1的状态是resolved,then()会去执行f_ok,则p2的状态由f_ok的返回值决定。
○ 如果f_ok返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_ok函数的return值。
○ 如果f_ok返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
○ 如果f_ok这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
● 如果p1的状态是rejected,then()会去执行f_err,则p2的状态由f_err的返回值决定。
○ 如果f_err返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_err函数的return值。
○ 如果f_err返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
○ 如果f_err这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。

重点

示例代码1

var p1 = new Promise(()=>);
var p2 = p1.then(function f_ok(), function f_err()); // p2也是一个promise对象。
console.dir(p1); // pending
console.dir(p2); // pending

示例代码2

var p1 = new Promise((resolve,reject)=> resolve());
var p2 = p1.then(function f_ok()
	return 1
, function f_err()); // p2也是一个promise对象。
console.dir(p1);	// resolved, undefined
console.dir(p2); // resolved, 1

特殊地,如果f_ok()中并没有return语句,则相当于是 return undefined。

示例代码3

var p1 = new Promise((resolve,reject)=> resolve());
var p2 = p1.then(function f_ok()
	var temp = new Promise((resolve,reject)=> resolve(a:1) ); 
	return temp;
, function f_err()); 

console.dir(p2); // resolved, a:1

示例代码4

var p1 = new Promise((resolve,reject)=> resolve());
var p2 = p1.then(function f_ok()
	console.log(abc);// 这里故意犯错
, function f_err()); 

console.dir(p2);

示例代码5

var p1 = new Promise((resolve,reject)=> reject(100));
var p2 = p1.then(function f_ok()
	
, function f_err(errVal)
	var temp = new Promise((resolve,reject)=> resolve(b:errVal) ); 
	return temp;
); 

console.dir(p2);

示例代码6

var p1 = new Promise((resolve,reject)=> reject(100));
var p2 = p1.then(function f_ok()
	
, function f_err(errVal)
	throw new Error("aaa")
); 
console.dir(p2)

catch的格式及用法

catch 是 then(null, reject)的别名

catch()的格式及用法

Promise.prototype.catch 是 Promise.prototype.then(null, reject)的别名,用于指定当promise对象的状态从pending变成rejected的回调函数 。

var p1 = new Promise((resolve,reject)=>
	reject('s')
);

p1.catch(function(err)
	console.log(err);
)

// 与下面的代码等价
p1.then(null, function(err)
	console.log(err);
)

单独使用catch没有什么意义,它一般与then一块使用。如下:

new Promise((resolve,reject)=>

).then(function(result)
	// 如果promise对象的状态是resolved的,就会到这里来,并且result会接收promiseValue的值
).catch(function(err)
	// 如果promise对象的状态是rejected的,就会到这里来,并且err会接收promiseValue的值
)

// 上面的代码如何拆分来写的话,等价于:
var p1 = new Promise((resolve,reject)

);
var p2 = p1.then(function(result)

);
var p3 = p2.catch(function(err)

)

catch的返回值

catch的返回值仍是一个promise对象,确定它的值的方式与then(null,(errVal)=> )的方式一致。

promise的链式调用

链式调用: $().attr().val()

p.then().then().then()…

示例1

代码

function do1() 
    console.log("任务1");

function do2() 
    console.log("任务2");

function do3() 
     console.log("任务3");

function do4() 
    console.log("任务4");


var p = new Promise((resolve,reject)=> resolve())
p.then(do1)
 .then(do2)
 .then(do3)
 .then(do4);

结果输出是:任务1,任务2,任务3,任务4

分析

var p1 = p.then(do1);
var p2 = p1.then(do2)
var p3 = p2.then(do3)
var p4 = p3.then(do4)

第一步:由于p的状态是resolved,所以p.then(do1)中,do1函数会执行。输出任务1。

第二步:确定p1的状态。按前面关于then的部分的介绍,p1的状态由do1()来决定。因为do1并没有明确指定返回值,则返回值就是undefined. p1的状态就是resolved。

第三步:由于p1的状态是resolved,所以p1.then(do2)会继续执行do2。输出任务2 ,且p2的状态由do2来决定。与第二步的分析相同,p2的状态仍是resolved。

第四步:接下来看p3。由于p2的状态是resolved,所以p2.then(do3)会继续执行do3。输出任务2 ,且p3的状态由do3来定,仍是resolved。

最后:p3.then(do4)。由于p3的状态是resolved,所以执行do4。输出任务4 。

示例2

function do1() 
    console.log("任务1");

function do2() 
    console.log("任务2");

function do3() 
     console.log("任务3");

function do4() 
    console.log("任务4");


var p = new Promise((resolve,reject)=> resolve())
p.then(do1)
 .then(do2)
 .catch(do3)
 .then(do4);

上面的代码的执行结果是:任务1, 任务2, 任务4。我们先把过程分解一下,多添加几个中间变量来描述它们的值:

var p1 = p.then(do1);
var p2 = p1.then(do2)
var p3 = p2.catch(do3)
var p4 = p3.then(do4)

分析如下:

第一步:由于p的状态是resolved,所以p.then(do1)中,do1函数会执行。输出任务1。

第二步:确定p1的状态。按前面关于then的部分的介绍,p1的状态由do1()来决定。因为do1并没有明确指定返回值,则返回值就是undefined. p1的状态就是resolved。

第三步:由于p1的状态是resolved,所以p1.then(do2)会继续执行do2。输出任务2 ,且p2的状态由do2来决定。与第二步的分析相同,p2的状态仍是resolved。

第四步:接下来看p3。由于p2的状态是resolved,所以它并不会执行do3, p3的状态没有变化,仍保持p2的状态:resolved。

最后:p3.then(do4)。由于p3的状态是resolved,所以执行do4。输出任务4 。

示例3

function do1() 
    console.log("任务1");
    console.log(abc); //故意犯错
目录
  • 常见 Promise 面试题
  • Promise 出现的原因
  • 什么是 Promise
  • Promsie 与事件循环
  • Promise 的升级
  • 结语     

    Promise 出现的原因

    在 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 规范诞生了,并且在业界有了很多实现来解决回调地狱的痛点。比如业界著名的 Qbluebirdbluebird 甚至号称运行最快的类库。


    Promise的使用总结。

    Promise 这么多概念,初学者很难一下子消化掉,那么我们可以采取强制记忆法,强迫自己去记住使用过程。

    • 首先初始化一个 Promise 对象,可以通过两种方式创建, 这两种方式都会返回一个 Promise 对象。

      • 1、new Promise(fn)
      • 2、Promise.resolve(fn)
    • 然后调用上一步返回的 promise 对象的 then 方法,注册回调函数。

      • then 中的回调函数可以有一个参数,也可以不带参数。如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数。比如
          new Promise(fn)
          .then(fn1(value){
              //处理value
          })
      复制代码
    • 最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常。

    通常按照这三个步骤,你就能够应对绝大部分的异步处理场景。用熟之后,再去研究 Promise 各个函数更深层次的原理以及使用方式即可。



                    

以上是关于promise 以及经典面试题的主要内容,如果未能解决你的问题,请参考以下文章

JS手写面试题 --- Promise 以及相关方法的实现

promise 以及相关面试题

说说高频面试题 promise

Promise 必知必会经典题

JavaScript 总结面试笔记 + 经典面试题 + 常用算法,帮你搞定前端 JavaScript 面试!

JavaScript经典面试题详解