11、前端面试必考:ES6之promise详解 和 高频面试题

阮一峰博客 :https://es6.ruanyifeng.com/#docs/promise

目录

一、学习promise的前置条件

1.1 区别实例对象和函数对象

1.2 两种类型的回调函数

1.2.1 同步回调

1.2.2 异步回调

1.3 JS中的error处理(错误处理)

1.3.1 常见的内置错误

1.3.2 错误的处理

1.3.3 错误对象的属性

二、promise的理解和使用

2.1 promise是什么?

2.2 promise解决的问题?

2.3 promise的特点?

2.4 promise的状态改变?

2.5 promise的缺点?

2.6 promise的基本流程?

2.7 Promise的API

2.7.1 Promise.resolve()

2.7.2 Promise.reject()

2.7.3 Promise.all()

2.7.4 Promise.race()

2.7.5 Promise.any()

2.7.6 Promise.prototype.then() 

2.7.7 Promise.prototype.catch() 

2.7.8 Promise.prototype.finally() 

2.8 promise的使用

2.8.1 字节面试题,看代码写输出

2.8.2 字节面试题,看代码写输出 (与宏任务和微任务有关)

2.8.3 字节面试题,看代码写输出 

2.8.4 字节面试题,看代码写输出 (async 和 await)

2.8.5  字节面试题,看代码写输出 

2.8.6 字节面试题,看代码写输出 (与宏任务和微任务有关) 

2.8.7 js如何实现异步操作,浏览器事件循环机制(宏任务和微任务)

2.8.8 把 ajax 封装成 promise 的形式

2.8.9  将一个典型回调风格的功能函数变为promise风格

2.8.10 用Promise实现传统的回调函数,尝试捕获错误;如何让两个异步函数并发执行。

2.8.11 手写promise.all


一、学习promise的前置条件

  1.  回调函数
  2.  定时器
  3.  JS单线程
  4.  Ajax
  5.  异常处理
  6.  IO流

1.1 区别实例对象和函数对象

注意:

  • ()的左边是函数。例如:Fn()  ,Fn是函数
  • 点的左边是对象。 例如:Fn.prototype ,Fn是对象(若Fn是函数,则Fn就是函数对象,即将函数作为对象使用)

1.2 两种类型的回调函数

回调函数详解参考文章:https://blog.csdn.net/itcodexy/article/details/111027346

  • 回调函数是自己定义的
  • 回调函数不是自己调用

1.2.1 同步回调

理解:立即执行,完全执行完了才结束,不会放入回调队列中。主程序A会因回调函数C的执行而阻塞

例1:A.B(参数,回调函数C),主程序A调用函数B,并传入回调函数C,主程序A会等待回调函数C执行完成

例2:forEach中的回调函数执行完(不会放入队列中,一上来就执行),才会执行后面的操作

        

打印结果:

1.2.2 异步回调

理解:不会立即执行,会放入回调队列中将来执行。回调函数C的执行不会阻塞调用方A。

例1:A.B(参数,回调函数C)。把回调函数C放到另一个线程(进程)、甚至另一台机器上,因此回调函数的执行和我们主程序A的运行同时进行

例2:setTimeout()内的是回调函数,会放入队列将来执行,故先执行了其后面的内容。

打印结果:

1.3 JS中的error处理(错误处理)

1.3.1 常见的内置错误

1) ReferenceError:引用的变量不存在

2)TypeError:数据类型不正确的错误

3)RangeError:数值不在其所允许的范围内

4)SyntaxError:语法错误

1.3.2 错误的处理

1)捕获错误 try ... catch(捕获try中的异常)

2)抛出错误:throw error (将异常抛出)

调用该函数时,捕获抛出的异常,并打印

1.3.3 错误对象的属性

二、promise的理解和使用

2.1 promise是什么?

Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

1)所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。(这个事件通常是一个异步操作)

2)从语法上,Promise是一个构造函数,用来生成Promise实例。

3)从功能上,Promise对象用来封装一个异步操作,可以获取其结果。

4)Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

 

例1:生成Promise实例

1)Promise构造函数接受一个函数作为参数该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署;

2)resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去

3)reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去

例2:Promise实例生成后,用then方法接收resolved状态和rejected状态的回调函数

等价于下面的写法:

1)then方法可以接受两个回调函数作为参数。这两个函数的参数值对应promise实例中传入的参数。这两个函数都是可选的,不一定要提供。

2)第一个回调函数是:Promise对象的状态变为resolved时调用(即成功时调用

3)第二个回调函数是:Promise对象的状态变为rejected时调用(即失败时调用

补充:resolved统一只指fulfilled状态,不包含rejected状态。

2.2 promise解决的问题?

1) 解决回调地狱问题:在promise之前,我们使用回调函数来发送异步请求,回调函数的问题是当我们有很多请求,并且下一个请求要依赖上一个请求的结果,这样会导致我们的请求

层层嵌套,使得代码非常臃肿,可读性差,容易产生bug导致所谓的回调地狱。

解决:使用Promise的then方法

2)任何时候都可以得到fulfilled(已成功)或 rejected(已失败)的结果,如果状态改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件

(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的

2.3 promise的特点?

1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)只有异步操作的结果,可以决定当前

是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

2)一旦状态改变,就不会再变,并且任何时候都可以得到操作成功或失败的结果Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要

这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结

果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

3) Promise 新建后就会立即执行。下面代码中,Promise 新建后立即执行,所以首先输出的是“开始执行promise”。然后将在当前脚本所有同步任务执行完后then方法指定的回调函数才会执行所以“操作成功”最后输出

2.4 promise的状态改变?

  1. pending(进行中)-> fulfilled(已成功)
  2. pending(进行中)-> rejected(已失败)

注意:

  • 只有这两种状态改变,且一个promise对象只能改变一次状态;
  • 无论状态变为成功还是失败,都分别会有一个结果数据;

2.5 promise的缺点?

  1. 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  2. 其次,如果不设置回调函数(reject),Promise内部抛出的错误,不会反应到外部
  3. 第三,当处于pending(进行中)状态时无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

2.6 promise的基本流程?

注意:

  • 成功时,执行resolve(), 返回一个Promise 实例
  • 失败时,执行reject(),返回一个 Promise 实例,该实例的状态为rejected。
  • then() 返回的是一个新的Promise实例(不是原来的Promise实例),因此可以采用链式写法,即then方法后面再调用另一个then方法。

2.7 Promise的API

Promise自身的方法:

  • resolve() --- 返回一个Promise对象,其参数分为四种情况。
  • reject() ---  返回一个新的 Promise 实例, 其参数会原封不动地作为reject的理由,变成后续方法的参数(例如 reject(e).catch(console.log(e)))。
  • all() --- 将多个 Promise 实例p1、p2包装成一个新的 Promise 实例p全部成功,p才会成功;有一个失败p就失败,返回失败结果。以数组的形式返回所有promise的执行结果。
  • race() --- 将多个 Promise 实例p1、p2包装成一个新的 Promise 实例p。只要p1p2之中有一个先改变状态p的状态就跟着改变。先改变状态的返回值,传递给p的回调函数。
  • any() --- 将多个 Promise 实例p1、p2包装成一个新的 Promise 实例p。只要有一个成功,p就会成功,先成功的返回值传给p;所有都失败,p才会变成失败状态,返回错误AggregateError。
  • allSettled() ---  将多个 Promise 实例p1、p2包装成一个新的 Promise 实例p。只有等到p1,p2都返回结果,不管是fulfilled还是rejected,实例p才会结束。成功和失败的信息以数组的形式传给p

Pomise原型上的方法:

  • then() --- 用来对成功或失败的状态进行回调。 then方法的第一个参数是resolved状态的回调函数第二个参数是rejected状态的回调函数,它们都是可选的。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
  • catch() --- 用于指定发生错误时的回调函数。处理错误,可以放在链的最后,用来处理整条链的错误。跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。通俗的说法就是“Promise 会吃掉错误”。
  • finally() --- 不管 Promise 对象最后状态如何,都会执行finally。finally方法的回调函数不接受任何参数,表明finally方法里面的操作与状态无关,不依赖于 Promise 的执行结果。finally本质上是then方法的特例, 有了finally方法,不需要为成功和失败两种情况各写一次, 只需要写一次。

2.7.1 Promise.resolve()

根据参数不同,分为以下四种情况

1)参数是一个Promise的实例:Promise.resolve将不作任何修改,原封不动的返回这个实例。

2)参数是一个thenable对象:Promise.resolve方法会将这个对象转为Promise对象,然后立即执行thenable对象的then方法。

      thenable对象指的是具有then方法的对象

3)参数是一个原始值,或者是一个不具有then方法的对象:返回一个新的Promise对象,状态为Resolved

4)不带任何参数:直接返回一个状态为resolved的Promise对象,所以如果希望得到一个Promise对象,最直接的方法就是直接调用Promise.resolve方法。

第四种情况下,需注意:立即resolve的Promise对象是在本轮“事件循环”event loop结束时不是在下一轮“事件循环”开始时,如下面代码所示:

2.7.2 Promise.reject()

返回一个新的 Promise 实例, 其参数会原封不动地作为reject的理由,变成后续方法的参数(例如 reject(e).catch(console.log(e)))

2.7.3 Promise.all()

多个 Promise 实例p1、p2包装成一个新的 Promise 实例p全部成功,p才会成功;有一个失败p就失败,返回失败结果。

数组的形式返回所有promise的执行结果。

接受一个数组作为参数,p1p2p3都是 Promise 实例。

p的状态由p1p2p3决定,分成两种情况:

1)只有p1p2p3的状态都变成fulfilled(已成功)p的状态才会变成fulfilled此时p1p2p3返回值组成一个数组,传递给p的回调函数

2)只要p1p2p3之中有一个被rejected(已失败)p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

注意:如果p1定义了catch方法,被rejected了,不会触发Promise.all()catch方法

(因为p1执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,而不会触发Promise.all()catch方法。)

例子:Promise.all([p1,p2]) 。 p1自己捕获了错误,所以对p而言,p1和p2状态都是已成功

如果p1没有捕获错误,则p1失败,直接返回错误值,p视为失败。只打印错误信息

2.7.4 Promise.race()

 将多个 Promise 实例p1、p2包装成一个新的 Promise 实例p。只要p1p2之中有一个先改变状态p的状态就跟着改变。先改变状态的返回值,传递给p的回调函数。

例子:Promise.race([p1,p2]) 。p1先执行完,p随p1的状态改变

2.7.5 Promise.any()

 将多个 Promise 实例p1、p2包装成一个新的 Promise 实例p。

只要有一个成功,p就会成功,先成功的返回值传给p;所有都失败,p才会变成失败状态,返回错误AggregateError。

例子:Promise.any([p1,p2]) 。p1和p2都失败,返回一行错误提示。

2.7.6 Promise.prototype.then() 

用来对成功或失败的状态进行回调。 then方法的第一个参数是resolved状态的回调函数第二个参数是rejected状态的回调函数,它们都是可选的。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

如果还需要调用then方法,需要return一个结果,才能被下一个then方法拿到

例如:如果第一个then 不返回value,第二个then获取不到value。

注意:同一个promise对象多次then方法并不是promise链

2.7.7 Promise.prototype.catch() 

用于指定发生错误时的回调函数。处理错误,可以放在链的最后,用来处理整条链的错误。

问题:跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。通俗的说法就是“Promise 会吃掉错误”。

例如:p1对象出现错误,但是2s后,仍然执行了下面的代码,输出了123

2.7.8 Promise.prototype.finally() 

不管 Promise 对象最后状态如何,都会执行finallyfinally方法的回调函数不接受任何参数,表明finally方法里面的操作与状态无关,不依赖于 Promise 的执行结果finally本质上是then方法的特例, 有了finally方法,不需要为成功和失败两种情况各写一次, 只需要写一次。

例1:即使p1被rejected了,finally方法的回调函数仍然执行了

2.8 promise的使用

根据代码说出执行结果(大概20+道题)

https://blog.csdn.net/qq_42033567/article/details/108129645?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162071744216780265428419%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162071744216780265428419&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-108129645.pc_search_result_hbase_insert&utm_term=%E5%B8%B8%E8%A7%81%E7%9A%84promise%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98

2.8.1 字节面试题,看代码写输出

console.log('begin') 
setTimeout(() => {  console.log('setTimeout 1')  
Promise.resolve().then(() => {  
    console.log('promise 1')  
    setTimeout(() => {  console.log('setTimeout2 between promise1&2')  })  
}).then(() => {  
    console.log('promise 2')  
}) }, 0); 
console.log('end')

输出:begin -> end -> setTimeout 1 -> promise 1 -> promise 2 -> setTimeout2 between promise1&2

宏任务类型:包括整体代码script,setTimeout,setInterval(循环),setImmediate 

微任务类型:Promise,process.nextTick

2.8.2 字节面试题,看代码写输出 (与宏任务和微任务有关)

console.log(1);
setTimeout(() => {
 console.log(2);
 Promise.resolve().then(() => {
  console.log(3)
 });
});
new Promise((resolve, reject) => {
 console.log(4)
 resolve()
}).then(() => {
 console.log(5);
})
setTimeout(() => {
 console.log(6);
})
console.log(7);

输出:1 -> 4 -> 7 -> 5 -> 2 -> 3 -> 6

2.8.3 字节面试题,看代码写输出 

const first = () =>
new Promise((resovle, reject) => {
    console.log(1)
    const p = new Promise((resovle, reject) => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
            resovle(4)
        }, 0)
        resovle(5)
        })
    resovle(6)
    p.then(arg => {
        console.log(arg)
    })
})

first().then(arg => {
    console.log(arg)
})

console.log(7)

输出:1 ->  2 -> 7 -> 5 -> 6 -> 3

2.8.4 字节面试题,看代码写输出 (async 和 await)

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2 start');
  return new Promise((resolve, reject) => {
    resolve();
    console.log('async2 promise');
  })
}

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

async1();

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

console.log('script end')

输出:srcipt start -> async1 start -> async2 start  -> async2 promise -> promise1 -> srcipt end ->

promise2 -> promise3 -> async1 end -> setTimeout

1)例1

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')

输出结果:

async1 start
async2
start
async1 end

分析过程:

  • 首先执行函数中的同步代码async1 start,之后碰到了await,它会阻塞async1后面代码的执行,因为此会先去执行async2中的同步代码async2,然后跳出async1
  • 跳出async1函数后,执行同步代码start
  • 在一轮宏任务全部执行完之后,再来执行await后面的内容async1 end

这里可以理解为await后面的语句相当于放到了new Promise中下一行及之后的语句相当于放在Promise.then中

2)例2

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")

输出结果:

async1 start
async2
start
async1 end
timer2
timer3
timer1

分析过程:

  • 首先进入async1,打印出async1 start,这个是毋庸置疑的
  • 之后遇到async2,进入async2,遇到定时器timer2,加入宏任务队列,之后打印async2
  • 由于async2阻塞了后面代码的执行,所以执行后面的定时器timer3,将其加入宏任务队列,之后打印start
  • 然后执行async2后面的代码,打印出async1 end,遇到定时器timer1,将其加入宏任务队列
  • 最后,宏任务队列有三个任务,先后顺序为timer2,timer3,timer1,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。
     

3)例3

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

输出结果:

script start
async1 start
promise1
script end

 分析过程:

这里需要注意的是在async1await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,所以在await之后的内容是不会执行的,也包括async1后面的 .then

2.8.5  字节面试题,看代码写输出 

new Promise(() => {
    throw new Error() 
}).catch(() => {
    console.log(1);
}).then(() => {
    console.log(2);
})

输出结果:1 -> 2

2.8.6 字节面试题,看代码写输出 (与宏任务和微任务有关) 

console.log('script start');
setTimeout(function() {
 console.log('setTimeout');
}, 0);  
new Promise(function(resolve) {
 console.log('promise1');
 resolve();
}).then(function() {
 console.log('promise2');
}).then(function() {
 console.log('promise3');
});
console.log('script end');
// script start -> promise1 -> script end -> promise2 -> promise3 -> setTimeout

输出结果: script start -> promise 1 -> script end -> promise 2 -> promise 3 -> setTimeout

 

2.8.7 js如何实现异步操作,浏览器事件循环机制(宏任务和微任务)

(1)回调函数

概念: 封装了异步操作的函数接受一个匿名函数作为参数,当异步操作执行完后调用这个传递进来的匿名函数

缺点:容易出现多层的嵌套(回调地狱问题),例如ajax请求来的数据作为下一个ajax请求的参数,一个异步完成的结果作为另一个异步操作函数的参数

(2)promise

(3)async await

2.8.8 把 ajax 封装成 promise 的形式

为了简便起见没有考虑浏览器的兼容问题

1)使用get请求(在url尾部传递参数

function getJson(url){
        return new Promise((resolve, reject) => {
            //1. 创建XMLHttpRequest对象
            var xhr = new XMLHttpRequest();
            //2. 创建请求
            xhr.open("GET",url,true);
            //3. 监听readyState状态,获取数据
            xhr.onreadystatechange = function(){
                if(xhr.readyState === 4){//3.1已获取响应数据
                    if(xhr.status === 200){ //3.2服务器成功处理请求
                        resolve(JSON.parse(xhr.responseText)); //返回响应数据
                    }else{//3.3请求失败(需要返回失败时的状态码)
                        reject({
                            status : xhr.status,
                            data : JSON.parse(xhr.responseText)
                        });
                    }
                }
            }
            //4. 发送请求
            xhr.send();
        })
    }

2)使用post请求(send方法中传递参数,必须设置请求头content-type

function getJson(url,data){
        return new Promise((resolve, reject) => {
            //1. 创建XMLHttpRequest对象
            var xhr = new XMLHttpRequest();
            //2. 创建请求
            xhr.open("POST",url,true);
            //3. 设置请求头content-type
            xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
            //4. 监听readyState状态,获取数据
            xhr.onreadystatechange = function(){
                if(xhr.readyState === 4){//3.1已获取响应数据
                    if(xhr.status === 200){ //3.2服务器成功处理请求
                        resolve(JSON.parse(xhr.responseText)); //返回响应数据
                    }else{//3.3请求失败(需要返回失败时的状态码)
                        reject({
                            status : xhr.status,
                            data : JSON.parse(xhr.responseText)
                        });
                    }
                }
            }
            //4. 发送请求
            xhr.send(JSON.stringify(data));
        })
    }

2.8.9  将一个典型回调风格的功能函数变为promise风格

2.8.10 用Promise实现传统的回调函数,尝试捕获错误;如何让两个异步函数并发执行。

这里实际上问的很基础了,改写回调函数直接new 一个Promise对象,判断状态执行resolve跟reject即可,后面捕获错误我直接在Promise里面捕获了,后续了解到在Promise外部 await的时候捕获更好一点。异步函数并发执行直接采用Promise.all([]),即可实现。

传统异步函数,必须传入回调函数。

(1)方式一:有了Promise之后,就这么封装一下

连续.then().then()没有回调黑洞了,变成了火车黑洞。

(2)方式二:sync/await

它能够等异步执行完毕,再执行后面的语句。

它能够让我们以写同步的方法写异步:

2.8.11 手写promise.all

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值