面试常问的promise及async,await语法糖,你真的懂了么?

396 阅读8分钟

何为promise?

它是一个ES6提出一个新语法,用来优化异步代码的写法,可以解决回调地狱问题。

在没有它之前,javascript中的异步处理,大多是利用回调函数来实现的。典型的几种如下:(1)setTimeout (2)ajax (3)nodejs中的文件读取。现在有了promise,就可以对这些异步操作进行改写了。

关于promise的面试题也是经常问到的,直接上代码:

问题一:

目标是让sleep 的功能与setTimeout一样:就是等2000毫秒之后再执行后续操作。

function sleep(time){
    // 请写出你的代码
}
​
sleep(2000).then(()=>{
    console.log("后续操作")
})
console.log(2);

问题二:

最终打印的值为多少

function increment(value) {
  return value + 1;
}
function doubleUp(value) {
  return value + 1;
}
function threeUp(value){
  return value * 2;
}
function output(value) {
  console.log(value);
}
var p = Promise.resolve(1);
p.then(increment)
.catch(doubleUp)
.then(threeUp)
.then(output)

要答出以上问题,需先了解promise的原理

Promise的基本使用

promise相当于一个承诺,在未来的某一时刻,这个承诺会成功兑现,又或者失败

它的经典使用方式如下:

let 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),这两个形参也是函数。在格式上,也可以采用箭头函数来改写。例如:let p1 = new Promise((resolve,reject)=>{})`。
  • 在函数体的内部, 一般会执行异步代码,然后根据情况来调用resolve()或者是reject() 。调用resolve或者是reject后会产生什么样的后果,在后面小节介绍。 当然resolve和reject只是形参名,可以改写成其它的。

promise的三种状态

它有三种状态:

1.初始态pending(承诺许下的那一刻)

2.成功态resolved,也有叫fulfilled的。(承诺兑现)

3.失败态rejected(承诺失败)

promise状态是可以改变的,但只能是以下两种转变

  • pending 变为 resolved

    let p = new Promise((resolve,reject)=>{ resolved();})
    

    当在函数体中调用了resolve方法,就将promise的状态变为resolved

  • pending 变为 rejected

    let p = new Promise((resolve,reject)=>{ reject()} )
    

    当在函数体中调用了reject方法,就将promise的状态变为rejected

注意:

只有这两种 且promise对象只能改变一次(不可逆)

无论成功失败 都会有一个结果数据

promise的兑现

promise的状态变化后,就到了兑现承诺的时刻

成功就进入then中,调用函数,并把resolve(实参)函数中的值接收过来 image.png 失败就进入catch中,调用函数,并把reject(实参)函数中的值接收过来

image.png

补充:

finally是不管状态是成功还是失败,都会执行的函数,根据实际情况的需要,也可以不加上finally()

解决问题一:

目前为止,我们可以完美解决问题一了

思路分析:

sleep可以调用.then方法,那么函数内部一定要返回一个promise对象,

而要想进入then内部,promise的状态变为成功即可,我们可以在2秒后,把promise状态变为resolved

代码实现:

 function sleep(time) {
    return new Promise((resolve, reject) => {
      setTimeout(()=>{
        resolve()
      },time)
    })
  }
​
  sleep(2000).then(() => {
    console.log('后续的操作.....')
  })
  console.log(2) 

then的返回值

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

let p1 = new Promise(()=>{});
let p2 = p1.then(function f_ok(){}, function f_err(){}); 

那么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:

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

示例2:

let p1 = new Promise((resolve,reject)=>{ resolve()});
let 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:

let p1 = new Promise((resolve,reject)=>{ resolve()});
let 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}

promise的链式调用

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

解决这两个简单的示例再看问题二

解决问题二:

function increment(value) {
  return value + 1;
}
function doubleUp(value) {
  return value + 1;
}
function threeUp(value){
  return value * 2;
}
function output(value) {
  console.log(value);
}
var p = Promise.resolve(1);
p.then(increment)
.catch(doubleUp)
.then(threeUp)
.then(output)

补充:

Promise.resolve()可以直接得到一个成功状态的promise对象,直接在.resolve()里传参

相同的,Promise.reject()可以直接得到一个失败状态的promise对象

分析如下:

第一步:先得到一个成功状态的promise对象p,并把1传给then里面

第二步:调用increment函数,接收实参后return 的值 为2,因返回的不是promise对象,得到的新promise的状态为resolved,那么不走catch中的代码,也就没有执行catch中的doubleUp函数

第三步:成功状态会进入第二个then调用threeUp,返回4,同理,新的promise对象状态为resolved,进入第三个then

第四步:拿到传来的值,打印4

所以打印结果为4

async-await语法

async,await 是es7中新增的语法,用来进一步改进异步代码的写法,是promise升级版

async

async函数返回一个 Promise 对象。

async函数内部return语句返回的值是Promise 对象的值

async修饰的函数帮我们自动new了一个promise对象

直接return值(非promise对象),就相当于执行了resolve(),值为return的值

return的为promise对象时,与return后面的promise对象状态一致

function f1 () {
  return 1
}
async function f2 () {
  return 1
}
async function f3 () {}
const r1 = f1()
const r2 = f2()
const r3 = f3()
console.log(r1) // 1
console.log(r2) // Promise, resolved(1)
console.log(r3) // Promise, resolved(undefined)
r2.then(res => { console.log(res) })
r3.then(res => { console.log(res) })

await 命令

await的外层函数必须有一个async.

正常情况下,await命令后面是一个 Promise 对象,返回该promise的值。如果不是 Promise 对象,就直接返回对应的值。

<script>
    async function f() {
        const p = new Promise((resolve,reject)=>{
            resolve(100)
        })
        const a = 1
    
      // await 等待promise的状态变化完成(pending->resolved, pending->rejected)
        // 取出promise的值
        const b = await p 
​
        console.log(a,b)
    }
​
    f()
</script>

常一起使用来优化异步代码

如:axios请求

async function getData(){
            const data1 = await axios.get('url地址1')
            const data2 = await axios.get('url地址2')
            const data3 = await axios.get('url地址3')
        }
 getData()
 // data1数据得到后再发起第二个请求
    第二个请求拿到数据后,再发起第三个请求