web前端高级-vue

【前端面试】理解 Promise 和 Async

2021-02-22  本文已影响0人  老鼠AI大米_Java全栈

面试碰到这些问题时,这样回答
1、面试官:“你接受免费加班吗?”
程序员:“我上班不要工资。”
面试官:“你开玩笑?”
程序员:“是你先开玩笑的。”
2、程序员:今天我去一家公司面试,面试官说 “给你五秒 让我记住你”
说完我抡圆了给了他一巴掌,打完我就走。
刚到家面试通知就来了
3、面试官:你最擅长什么?
程序员:我擅长在适当的时候说“不行”。
面试官:举个例子。
程序员:不行!
4、面试官:你可以在公司干多久?
程序员:可以干到公司倒闭!
5、面试官:“熟悉哪种语言”。
程序员:“JavaScript”。
面试官:“知道什么叫类么”。
程序员:“我这人实在,工作努力,不知道什么叫累”。
面试官:“知道什么是包?”。
程序员:“我这人实在 平常不带包 也不用公司准备了”。
面试官:“知道什么是继承么”。
程序员:“我是孤儿没什么可以继承的”。
面试官:“知道什么叫对象么?”。
程序员:“知道,不过我工作努力,上进心强,暂时还没有打算找对象”。
据说这是面试时你经常的答案。。。看完这篇文章,让面试不在尴尬!!!

一、Promise

凡事有因必有果,新事物的出现就代表着老的事物不能满足我们的需求了。
Promise 这个新事物就是在这个背景下出现的,而它代替的老事物就是ES6 之前经常被用的 callback(回调函数)。

1)、回调地狱

什么是回调地狱?

setTimeout(() => {
  console.log(1);
  setTimeout(() => {
      console.log(2);
      setTimeout(() => {
      console.log(3);
      setTimeout(() => {
       console.log(4);
        //无限延伸
       }, 4000);
      }, 3000);
   }, 2000);
}, 1000);

一般来说回调地狱就是出现在异步操作中,下一次的操作依赖上一次的结果,一环套一环,套着套着就套的我们头痛难忍,写出了上面的代码。
虽说上面的这种情况不会真实的出现在项目中,但实际开发中为了拿到数据调用多个接口的情况是很常见的,比如要获取图片的路径,需要先发送第一个请求,拿到图片的ID,然通过图片的ID拿到图片的URL,这样第二个请求需要在第一个请求完成后执行;

$.ajax({
  url: "a.json",
  data: {},
  success: function() {
    $.ajax({
        url: "b.json",
        data: {},
        success: function() {
            $.ajax({
               url: "c.json",
               data: {},
               success: function() {
                 $.ajax({
                  url: "d.json",
                  data: {},
                  success: function() {
                  }
             })
             }
           })
        }
    })
  }
})

2)、Promise 解决异步避免回调地狱

Promise 的出现解决了这个问题,先来看看 Promise 怎么解决回调地狱的。

// 链式写法
var P = new Promise(function(resolve ,reject){
   if(true){ //请求 成功
       resolve('ok' )
   }else {
       reject( ' error ')
   }
});
p. then( function(v){
    console .1og(v);
    return new Promise(function(reso1ve , reject){
          if(true){ //请求成功
               resolve('ok' )
          }else{
               reject( ' error' )
          }
    });
},function(v){
    console.log(v)
}). then(function(v){
    console.log(1)
}, function(v){
   console.log(2)
}). then(function(v){
    console.log(3)
}, function(v){
   console.log(4)
})

3)、Promise 基础

Promise 对象用于表示一个异步操作的最终完成 (或失败),及其结果值。Promise 对象是一个代理对象(代理一个值),被代理的值在 Promise 对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的 promise 对象。

4)、Promise 状态

一个 Promise 对象值是未知的,状态是可变的,但是无论怎么变化,它的状态永远处于以下三种之间:

5)、Promise使用

JS 万物皆对象,所以 Promise 也可以被我们new出来。我们通过下面的语法来新建一个 Promise 对象:
new Promise( function(resolve, reject) {...} /* executor */ );
Promise 的构造函数有一个参数 —— 是一个带有两个参数(resolve, reject)的函数,这两个参数分别代表此次异步操作的结果也就是Promise的状态。resolve和reject函数被调用时,分别会将此次 Promise 的状态改成fulfilled或者rejected,一旦异步操作结束,Promise 的最终状态只能是二者之一,如果异步成功,该状态会被resolve函数修改为fullfilled;相反当异步过程中抛出一个错误,那么该状态就会被reject函数改成rejected。

6)、Promise API

Promise 的原型链以及对象本身有一些方法供我们使用,其中最常用也比较有可说性的就是下面这几个:
then —— Promise.prototype.then(onFulfilled, onRejected)
添加解决(fulfillment)和拒绝(rejection)回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来 resolve。

new Promise( (resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
}). then(res => {
    console.1og(res);
});
new Promise((resolve, reject) => {
    setTimeout(() => reject(2), 1000);
}). then(res => {
    console.1og(res);
});

可以看到,.then里面拿到的是我们 Promise resolve 过后的数据。并且他还会返回一个 Promise 继续供我们调用,比如:

new Promise((resolve, reject) => { 
    setTimeout(() => resolve(1), 1000);
}). then(res => {
    console.log(res); //结果1
    return res ;
}). then(res => {
    console. log(res);
});

then()用法比较简单,大家肯定也经常用,这里其实就知道.then()是可以一直链式调用的,因为它的返回值也是一个 Promise,就可以了。

catch -- Promise.prototype.catch(onRejected)

添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的 promise。当这个回调函数被调用,新 promise 将以它的返回值来 resolve,否则如果当前 promise 进入 fulfilled 状态,则以当前 promise 的完成结果作为新 promise 的完成结果。

new Promise((resolve, reject) => {
    setTimeout(() => reject(1), 1000);
}). then(res => {
    console.1og( ' then: ' , res);
}). catch(error => {
    console.og( 'catch:', error);
});\

all —— Promise.all(iterable)

这个方法返回一个新的 promise 对象,该 promise 对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。这个新的 promise 对象在触发成功状态以后,会把一个包含 iterable 里所有 promise 返回值的数组作为成功回调的返回值,顺序跟 iterable 的顺序保持一致;如果这个新的 promise 对象触发了失败状态,它会把 iterable 里第一个触发失败的 promise 对象的错误信息作为它的失败错误信息。Promise.all 方法常被用于处理多 个promise 对象的状态集合。
这个算是我经常使用的一个 API 了,上面的内容虽然有点长,但是总结起来其实也很简单,大概就是如下三个方面:

第一:接收一个 Promise 对象数组作为参数

function fun1() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(1), 1000);
    }). then(res => console. log(res));
}
function fun2() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(2), 2000);
    }). then(res => console. log(res));
}
function fun3() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(3), 3000);
    }). then(res => console. log(res));
}
Promise .all([fun1, fun2, fun3]);

第二:参数所有回调成功才是成功,返回值数组与参数顺序一致

function fun1() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(1), 1000);
    }). then(res => console. log(res));
}
function fun2() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(2), 2000);
    }). then(res => console. log(res));
}
function fun3() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(3), 3000);
    }). then(res => console. log(res));
}
Promise .all([fun1(), fun2(), fun3()]) . then(res => {
    console .1og(res);
});

第三:参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

function fun1() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(1), 1000);
    }). then(res => console. log(res));
}
function fun2() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(2), 2000);
    }). then(res => console. log(res));
}
function fun3() {
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(3), 3000);
    }). then(res => console. log(res));
}
Promise .all([fun1(), fun2(), fun3()]) . then(res => {
    console .1og(res);
}).catch(error => {
    console.log(error);
});

Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来;
常见问题
setTimeout 和 Promise 都是异步操作,那么谁更快呢?

function fun() {
    setTimeout(() => console .1og( ' settimeout' ), 0);
    new Promise(() => {
       console .1og( ' promise');
    })
}
image.png
总结:promise 无关顺序更快执行

二、Async/Await

还得再来一遍,新事物的出现就代表着老的事物不能满足我们的需求,ES6 刚出 Promise 来解决异步问题,ES7 就又出了一个 Async/Await(其实官方名字是 async function),看来 Promise 并没有达到大家伙的预期,所以官方就又搞了个更为优雅的异步解决方案。
为什么说它是为了解决 Promise 带来的问题,可以看看 MDN 官网的下面这段话:
async/await 的目的是简化使用多个 promise 时的同步行为,并对一组 Promises 执行某些操作。正如 Promises 类似于结构化回调,async/await 更像结合了 generators 和 promises。

1)、Promise 并不是完美的解决方案

上面提到的那个异步嵌套 setTimeout的例子来说,事实上,大部分人用 Promise 应该并不会像上面的代码那样写,而是下面这样:

new Promise((resolve, reject) => 
    setTimeout(() => resolve(1), 1000);
}).then(res => {
    console .1og(res);
    new Promise((reso1ve, reject) => {
        setTimeout(() => resolve(2), 2000);
    }).then(res => {
       console .1og(res);
       new Promise((reso1ve, reject) => {
          setTimeout(() => resolve(3), 3000);
       }).then(res => {
         console .1og(res);
         new Promise((reso1ve, reject) => {
          setTimeout(() => resolve(4), 4000);
       }).then(res => {
          console .1og(res);
       });
     });
   })
})

其实 Promise.then() 如果使用过多,依然还是回调地狱,嵌套依然没有消失,所以来说,Promise 并不能称之为完美的异步方案,ES7 提出了 async function,它用来更为优雅的解决异步

function fun1() 
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(1), 1000);
    }). then(res => console .1og(res));
}
function fun2() 
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(2), 2000);
    }). then(res => console .1og(res));
}
function fun3() 
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(3), 3000);
    }). then(res => console .1og(res));
}
function fun4() 
    return new Promise( (resolve, reject) => {
       setTimeout(() => resolve(4), 4000);
    }). then(res => console .1og(res));
}
async function fun5() {
    await fun1(); //开始执行第一个异步函数
    await fun2(); //第一个执行完,开始执行第二个异步函数
    await fun3(); //第二个执行完,开始执行第三个异步函数
    await fun4(); //第三个执行完,开始执行第四个异步函数
}
fun5();

2)、async function 理解

关于 async function,其实并没有过多的 API,因为它更像是一个高级语法糖,官方文档给出的也更多都是使用示例。在这里,其实我们只需要知道并强调一件事 —— await 关键字用来暂停等待异步函数的执行结束,如果是 Promise,也就是等待它的 settled 状态,并且 await 只能出现在 async function 内部,不可单独使用。
官方给出了一个比较有意思的例子:

// 一个1秒的异步函数
var resolveAfter1Second = function() {
    console .1og("starting fast promise");
    return new Promise(resolve => {
       setTimeout(function() {
           resolve("fast");
           console .1og("fast promise is done");
      }, 1000);
 });
}
//一个2秒的异步函数
var resolveAfter2Seconds = function() {
    console .1og("starting slow promise");
    return new Promise(resolve => {
        setTimeout(function() {
           resolve("slow");
           console .1og("slow promise is done");
       }, 2000);
    });
}

另一种

//下面这种写法是 一起执行异步函数,只不过因为await 等待导政输出有先后
var concurrentStart = async function() {
    console . log( '==CONCURRENT START with awate=');
    const slow = resolveAfter2Seconds(); // starts timer immediately
    const fast = resolveAfter1second(): // starts timer immediately
    // 1. Execution gets here almost instantly
    console .1og(await slow): // 2. this runs 2 seconds after 1.
    console.log(await fast); // 3. this runs 2 seconds after 1.. imnediately after 2.. since fast is aiready resolved
}

// 下面这种是标准的等待写法
var sequentialStart = asyne functien() {
    console. log('==SEQUENTIAL STARTEE');
    // 1. Execution gets here alnost instantly
   const slow = await resolveAfter2Seconds();
   console.1og(slou); // 2. this runs 2 seconds after 1.
   const fast = await resolveAfter1second(); 
   console.1og(fast); // 3. this runs 3 seconds after 1.
}

第二种没什么可说的,想象中就是这个样子,因为 await 会暂停等待函数执行完之后再向下执行,因此等待时间不会重叠,先等待2秒执行 slow 后再等待1秒执行 fast。
而第一种

const slow = await resolveAfter25econds();
const fast = await resolveAfterisecond(); 
console.log(await slow) ;
console.log(await fast);

上面这两个异步函数因为没有 await 关键字,都是立即执行,因此先输出promise start,之后,两个函数延时不同,虽然 slow 先执行,但是是2秒,而 fast 后执行是1秒,先输出fast done再输出slow done。最后,await 关键字发挥作用,虽然 fast 先执行完,但是你还是要等 await slow 完事之后才能 await fast。

上一篇下一篇

猜你喜欢

热点阅读