前端面试过程当中,基本都会问到 Promise,若是你足够幸运,面试官问的比较浅,仅仅问 Promise 的使用方式,那么恭喜你。事实上,大多数人并无那么幸运。因此,咱们要准备好九浅一深的知识。前端
不知道读者有没有想过,为何那么多面试官都喜欢问Promise?能够思考一下哦~node
咱们看一些 Promise 的常见面试问法,由浅至深。面试
这些问题,若是你都能 hold 住,那么面试官基本承认你了。带着上面这些问题,咱们往下看。ajax
在 Promise 出现之前,咱们处理一个异步网络请求,大概是这样:编程
// 请求 表明 一个异步网络调用。 // 请求结果 表明网络请求的响应。 请求1(function(请求结果1){ 处理请求结果1 })
看起来还不错。
可是,需求变化了,咱们须要根据第一个网络请求的结果,再去执行第二个网络请求,代码大概以下:json
请求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){ ... }) }) }) }) }) })
这回傻眼了。。。 臭名昭著的 回调地狱 现身了。promise
更糟糕的是,咱们基本上还要对每次请求的结果进行一些处理,代码会更加臃肿,在一个团队中,代码 review 以及后续的维护将会是一个很痛苦的过程。浏览器
回调地狱带来的负面做用有如下几点:网络
出现了问题,天然就会有人去想办法。这时,就有人思考了,能不能用一种更加友好的代码组织方式,解决异步嵌套的问题。
let 请求结果1 = 请求1(); let 请求结果2 = 请求2(请求结果1); let 请求结果3 = 请求3(请求结果2); let 请求结果4 = 请求2(请求结果3); let 请求结果5 = 请求3(请求结果4);
相似上面这种同步的写法。 因而 Promise 规范诞生了,而且在业界有了不少实现来解决回调地狱的痛点。好比业界著名的 Q 和 bluebird,bluebird 甚至号称运行最快的类库。
看官们看到这里,对于上面的问题 2 和问题 7 ,心中是否有了答案呢。^_^
Promise 是异步编程的一种解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 归入进规范中。
仍是使用上面的网络请求例子,咱们看下 Promise 的常规写法:
new Promise(请求1) .then(请求2(请求结果1)) .then(请求3(请求结果2)) .then(请求4(请求结果3)) .then(请求5(请求结果4)) .catch(处理异常(异常信息))
比较一下这种写法和上面的回调式的写法。咱们不难发现,Promise 的写法更为直观,而且可以在外层捕获异步函数的异常信息。
Promise 的经常使用 API 以下:
类方法,该方法返回一个以 value 值解析后的 Promise 对象 一、若是这个值是个 thenable(即带有 then 方法),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled)
二、若是传入的 value 自己就是 Promise 对象,则该对象做为 Promise.resolve 方法的返回值返回。
三、其余状况以该值为成功状态返回一个 Promise 对象。
上面是 resolve 方法的解释,传入不一样类型的 value 值,返回结果也有区别。这个 API 比较重要,建议你们经过练习一些小例子,而且配合上面的解释来熟悉它。以下几个小例子:
//若是传入的 value 自己就是 Promise 对象,则该对象做为 Promise.resolve 方法的返回值返回。 function fn(resolve){ setTimeout(function(){ resolve(123); },3000); } let p0 = new Promise(fn); let p1 = Promise.resolve(p0); // 返回为true,返回的 Promise 便是 入参的 Promise 对象。 console.log(p0 === p1);
传入 thenable 对象,返回 Promise 对象跟随 thenable 对象的最终状态。
ES6 Promises 里提到了 Thenable 这个概念,简单来讲它就是一个很是相似 Promise 的东西。最简单的例子就是 jQuery.ajax,它的返回值就是 thenable 对象。可是要谨记,并非只要实现了 then 方法就必定能做为 Promise 对象来使用。
//若是传入的 value 自己就是 thenable 对象,返回的 promise 对象会跟随 thenable 对象的状态。 let promise = Promise.resolve($.ajax('/test/test.json'));// => promise对象 promise.then(function(value){ console.log(value); });
返回一个状态已变成 resolved 的 Promise 对象。
let p1 = Promise.resolve(123); //打印p1 能够看到p1是一个状态置为resolved的Promise对象 console.log(p1)
类方法,且与 resolve 惟一的不一样是,返回的 promise 对象的状态为 rejected。
实例方法,为 Promise 注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果,then 中的函数必定要 return 一个结果或者一个新的 Promise 对象,才可让以后的then 回调接收。
实例方法,捕获异常,函数形式:fn(err){}, err 是 catch 注册 以前的回调抛出的异常信息。
类方法,多个 Promise 任务同时执行,返回最早执行结束的 Promise 任务的结果,无论这个 Promise 结果是成功仍是失败。 。
类方法,多个 Promise 任务同时执行。
若是所有成功执行,则以数组的方式返回全部 Promise 任务的执行结果。 若是有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。
以上几种即是 Promise 的经常使用 API,掌握了这些,咱们即可以熟练使用 Promise了。
必定要多练习,熟练掌握,不然只知其一;不知其二的理解在面试时捉襟见肘。
为了便于理解 Promise,你们除了要多加练习之外,最好的方式是可以将Promise的机制与现实生活中的例子联系起来,这样才能真正获得消化。
咱们能够把 Promise 比做一个保姆,家里的一连串的事情,你只须要吩咐给他,他就能帮你作,你就能够去作其余事情了。
好比,做为一家之主的我,某一天要出门办事,可是我还要买菜作饭送到老婆单位(请理解我在家里的地位。。)
出门办的事情很重要,买菜作饭也重要。。但我本身只能作一件事。
这时我就能够把买菜作饭的事情交给保姆,我会告诉她:
咱们知道,上面三步都是须要消耗时间的,咱们能够理解为三个异步任务。利用 Promise 的写法来书写这个操做:
function 买菜(resolve,reject) { setTimeout(function(){ resolve(['西红柿'、'鸡蛋'、'油菜']); },3000) } function 作饭(resolve, reject){ setTimeout(function(){ //对作好的饭进行下一步处理。 resolve ({ 主食: '米饭', 菜: ['西红柿炒鸡蛋'、'清炒油菜'] }) },3000) } function 送饭(resolve,reject){ //对送饭的结果进行下一步处理 resolve('老婆的么么哒'); } function 电话通知我(){ //电话通知我后的下一步处理 给保姆加100块钱奖金; }
好了,如今我整理好了四个任务,这时我须要告诉保姆,让他按照这个任务列表去作。这个过程是必不可少的,由于若是不告诉保姆,保姆不知道须要作这些事情。。(我这个保姆比较懒)
// 告诉保姆帮我作几件连贯的事情,先去超市买菜 new Promise(买菜) //用买好的菜作饭 .then((买好的菜)=>{ return new Promise(作饭); }) //把作好的饭送到老婆公司 .then((作好的饭)=>{ return new Promise(送饭); }) //送完饭后打电话通知我 .then((送饭结果)=>{ 电话通知我(); })
至此,我通知了保姆要作这些事情,而后我就能够放心地去办个人事情。
请必定要谨记:若是咱们的后续任务是异步任务的话,必须return 一个 新的 promise 对象。
若是后续任务是同步任务,只需 return 一个结果便可。
咱们上面举的例子,除了电话通知我是一个同步任务,其他的都是异步任务,异步任务 return 的是 promise对象。
除此以外,必定谨记,一个 Promise 对象有三个状态,而且状态一旦改变,便不能再被更改成其余状态。
Promise 这么多概念,初学者很难一会儿消化掉,那么咱们能够采起强制记忆法,强迫本身去记住使用过程。
首先初始化一个 Promise 对象,能够经过两种方式建立, 这两种方式都会返回一个 Promise 对象。
而后调用上一步返回的 promise 对象的 then 方法,注册回调函数。
new Promise(fn) .then(fn1(value){ //处理value })
最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常。
一般按照这三个步骤,你就可以应对绝大部分的异步处理场景。用熟以后,再去研究 Promise 各个函数更深层次的原理以及使用方式便可。
看到这里以后,咱们便能回答上面的问题 4 和问题 5了。
Promise在初始化时,传入的函数是同步执行的,而后注册 then 回调。注册完以后,继续往下执行同步代码,在这以前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,若是有,那么执行,若是没有,继续下一个事件循环。
关于 Promise 在事件循环中还有一个 微任务的概念(microtask),感兴趣的话能够看我这篇关于nodejs 时间循环的文章 剖析nodejs的事件循环,虽然和浏览器端有些不一样,可是Promise 微任务的执行时机相差不大。
ES6 出现了 generator 以及 async/await 语法,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致。上面的列子能够写成这样:
(async ()=>{ let 蔬菜 = await 买菜(); let 饭菜 = await 作饭(蔬菜); let 送饭结果 = await 送饭(饭菜); let 通知结果 = await 通知我(送饭结果); })();
是否是更清晰了有没有。须要记住的是,async/await也是基于 Promise 实现的,因此,咱们仍然有必要深刻理解 Promise 的用法。
相信各位看官看到这里也累了,同时限于篇幅,本文再也不对手动实现 Promise 进行讲解了,留待下一篇文章~
上面的内容但愿读者多作练习,吃透 Promise 的使用与原理,让面试更加从容。