前言

我们先看看这几个来自大厂的面试题

面试题1:

const promise = new Promise(function(resolve,reject){
console.log(1)
resolve()
console.log(2)
})
console.log(3)

面试题2:

setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5);

面试题3:

Promise.resolve(1)
.then((res)=>{
console.log(res)
return 2
})
.catch( (err) => 3)
.then(res=>console.log(res))。

面试题4:

Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( () => 1)
.then( (x) => x + 1)
.then((x) => console.log(x))
.catch( (x) => console.log(error))

如果你看完这些题一脸懵逼,恭喜你,你可以继续往下看了,else,出门左拐大佬。

首先简单介绍一些Promise

Promise简介

Promises对象是CommonJS工作组提出的一种规范,目的是为异步操作提供统一接口

那么,什么是Promises?首先,它是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样;其次,它起到代理作用(proxy),使得异步操作具备同步操作(synchronous code)的接口,即充当异步操作与回调函数之间的中介,使得程序具备正常的同步运行的流程,回调函数不必再一层层包裹起来。

简单说,它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。

Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。

下图是一张Promise的API结构图。(引自https://github.com/leer0911/myPromise)

面试题解析

面试题1:

const promise = new Promise(function(resolve,reject){
console.log(1)
resolve()
console.log(2)
})
console.log(3)
// 1
// 2
// 3

解析

Promise类似于XMLHttpRequest,从构造函数Promise来创建一个新Promise对象作为接口。

要想创建一个Promise对象吗可以使用new来调用Promise的构造器来进行实例化。

var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});

new Promise的时候, 需要传递一个executor执行器 ,执行器函数会默认被内部所执行。借用VSCode编辑器我们可以看到一些内部的参数和返回值。

new Promise内部的执行器会立即执行它里面的代码,这里的 resolve()并不会阻塞下面的代码执行,我们可以理解new Promise(function(){...})这个就是一段同步代码而已。所以第一道题的答案显而易见。

巩固一下,下面的代码你一定可以准确的得出结果了。

setTimeout(function () {
console.log('setTimeout')
}, 0); let p = new Promise(function (resolve,reject) {
resolve();
console.log('a')
});

结果是:

// a
// setTimeout

这里会有EventLoop的一些知识,补充知识链接:说一说javascript的异步编程,到目前来说可以得到两点:

1. 我们完全可以把`new Promise(function(){...})`看成是同步代码。
2. `Promise`会优先于`setTimeout`执行。

约定

不同于老式的传入回调,在应用 Promise 时,我们将会有以下约定:

  • 在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
  • 通过 .then 形式添加的回调函数,甚至都在异步操作完成之后才被添加的函数,都会被调用。
  • 通过多次调用 .then,可以添加多个回调函数,它们会按照插入顺序并且独立运行。

    因此,Promise 最直接的好处就是链式调用

引自MND

面试题2:

setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5); // 2
// 3
// 5
// 4
// 1

解析:

Then 方法

可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolvereject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。

then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。

MDN中对于promise的介绍一个小案例非常的简单易懂,如下:

一个常见的需求就是连续执行两个或者多个异步操作,这种情况下,每一个后来的操作都在前面的操作执行成功之后,带着上一步操作所返回的结果开始执行。我们可以通过创造一个 Promise chain 来完成这种需求。

then 函数会返回一个新的 Promise,跟原来的不同:

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

或者

const promise2 = doSomething().then(successCallback, failureCallback);

第二个对象(promise2)不仅代表doSomething()函数的完成,也代表了你传入的 successCallback 或者failureCallback 的完成,这也可能是其他异步函数返回的 Promise。这样的话,任何被添加给 promise2 的回调函数都会被排在 successCallbackfailureCallback 返回的 Promise 后面。

特别重申一下,then 函数一定返回一个新的 Promise,跟原来的不同,下面的是错误的应用:

Promise对象的运行结果,最终只有两种。

  • 得到一个值,状态变为fulfilled
  • 抛出一个错误,状态变为rejected
promise.then(onFulfilled, onRejected);

promise对象的then方法用来添加回调函数。它可以接受两个回调函数,第一个是操作成功(fulfilled)时的回调函数,第二个是操作失败(rejected)时的回调函数(可以不提供)。一旦状态改变,就调用相应的回调函数。

onFulfilledonRejected 都是可选参数。

  • 如果 onFulfilled 是函数,当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值,在 promise 执行结束前其不可被调用,其调用次数不可超过一次

  • 如果 onRejected 是函数,当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因,在 promise 被拒绝执行前其不可被调用,其调用次数不可超过一次

  • onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码 ( 指的是引擎、环境以及 promise 的实施代码 )时才可被调用

  • 实践中要确保 onFulfilledonRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。

  • onFulfilledonRejected 必须被作为函数调用即没有 this 值 ( 也就是说在 严格模式(strict) 中,函数 this 的值为 undefined ;在非严格模式中其为全局对象。)

  • then 方法可以被同一个 promise 调用多次

  • then 方法必须返回一个 promise 对象

为了避免意外,即使是一个已经变成 resolve 状态的 Promise,传递给 then 的函数也总是会被异步调用:

Promise.resolve().then(() => console.log(2));
console.log(1); // 1, 2

至此,我们再回过头来看面试题:

setTimeout(function () {
console.log(1);
}, 0)
new Promise(function (resolve) {
console.log(2);
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
})
console.log(5);
// 2
// 3
// 5
// 4
// 1

new Promise构造器之后,会返回一个promise对象,对于这个promise对象,我们调用他的then方法来设置resolve后的回调函数。

promise对象会在for循环满足i==99时被resolve(),这时then的回调函数会被调用。

基于第一道题的基础,我们知道最后被打印的一定是1,然后是2,接着是3,由于.then是一个异步函数,用来接受promise的返回结果,很显然应该是5setTimeout由于EventLoop的原因是最后执行,所以后面是4,最后是1.

面试题3:

Promise.resolve(1)
.then((res)=>{
console.log(res)
return 2
})
.catch( (err) => 3)
.then(res=>console.log(res)) // 1
// 2

解析:

New Promise的快捷方式

静态方法Promise.resolve(value)可以认为是new Promise()方法的快捷方式。

比如Promise.resolve(1);,可以认为是一下代码的语法糖:

new Promise(function(resolve){
resolve(1);
});

在这段代码中的 resolve(1); 会让这个 promise 对象立即进入确定(即resolved)状态,

并将 1 传递给后面then里所指定的 onFulfilled 函数。

方法Promise.resolve(value) 的返回值也是一个promise对象,所以我们可以像下面那样

接着对其返回值进行.then 调用。

Promise.resolve(42).then(function(value){
console.log(value)
})

简单总结一下 Promise.resolve 方法的话,可以认为它的作用就是将传递给它的参数填 充(Fulfilled)到promise对象后并返回这个promise对象。

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。但如果这个值是个thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;否则以该值为成功状态返回promise对象。

语法为:

Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);

我们知道.then()同样是返回一个promise对象才能实现链式调用,所以连续的.then()是同样的道理,多个 then 方法调用串连在了一起,各函数也会严 格按照 resolve → then → then → then 的顺序执行,并且传给每个 then 方法的 value 的值都是前一个promise对象通过 return 返回的值。并且上面已经提到then 方法每次都会创建并返回一个新的promise对象。

Promise 的链式调用

then方法执行完会判断返回的结果,如果是promise会把这个promise执行,会取到它的结果。成功态(onFulfilled)和失败态(onRejected)。如果成功了就会把成功的结果传递给后面then里所指定的 onFulfilled 函数,失败了传递给onRejected函数。

Promise.resolve(1)
.then((value)=>{
console.log('value',value)
},
(reason)=>{
console.log('reason',reason)
})
// value 1
Promise.reject(2)
.then((value)=>{
console.log('value',value)
},
(reason)=>{
console.log('reason',reason)
})
// reason 2

上面的两个案例可以很清楚的看到,promise分别在成功态和失败态的时候传递给后面的then函数的调用输出结果。下一层的then是调成功还是失败是根据上面的promise返回的是成功还是失败决定的。

说到这里第三题的答案已经不用说了。

面试题4:

Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( () => 1)
.then( (x) => x + 1)
.then((x) => console.log(x))
.catch( (x) => console.log(error))
// 2

Catch 的后续链式操作

在一个失败操作(即一个 catch)之后可以继续使用链式操作,即使链式中的一个动作失败之后还能有助于新的动作继续完成。请阅读下面的例子:

new Promise((resolve, reject) => {
console.log('Initial'); resolve();
})
.then(() => {
throw new Error('Something failed'); console.log('Do this');
})
.catch(() => {
console.log('Do that');
})
.then(() => {
console.log('Do this whatever happened before');
});

输出结果如下:

Initial
Do that
Do this whatever happened before

注意,由于“Something failed”错误导致了拒绝操作,所以“Do this”文本没有被输出。

解析:

这道题唯一的疑惑可能是在第二个then这里,这个错误状态为什么没有被打印出来,我们知道catch是用来捕获错误的,但是这里catch是可以捕获到错误的,但是这段代码没有对捕获的错误进行处理而是继续返回了1作为下一个Promise的参数,所以在第三个then中我们获取到了一个1作为成功态,然后又对其进行+1处理返回给了下一个thenpromise的成功态,这时候最后一个then的第一个函数onFulfilled就能获取到一个value打印出来就是2,没有错误信息返回所以最后的catch没有输出。

我们也可以对上面的题改一种写法,就是另一种答案了:

Promise.resolve(1)
.then( (x) => x + 1 )
.then( (x) => {throw new Error('My Error')})
.catch( (err) => console.log(err))
.then( (x) => {
console.log(x)
return x + 1
})
.then((x) => console.log(x))
.catch( (x) => console.log(error)) // Error: My Error
at Promise.resolve.then.then
// undefined
// NaN

我们在第一个catch中对错误信息进行了处理,但是我们没有给下面的then返回一个成功态的结果,所以默认是undefined,这样就会导致后面的结果完全不一样。

大家可以把这些面试题进行多次变形,改写来去理解promise的执行顺序,以及参数传递,这样就能绕过更多的坑。

以上内容如果错误,欢迎指正,共同进步~

透过面试题来说说Promise的更多相关文章

  1. 透过面试题掌握HashMap【持续更新中】

    本文主要是自己阅读了HashMap和ConcurrentHashMap源码及一些Java容器类相关的博客后,找了一些很多面经中涉及到的Java容器相关的面试题,自己全部手写的解答,也花了一些流程图,之 ...

  2. 透过面试题掌握Redis【持续更新中】

    本文已收录到1.1K Star的Github开源项目<面试指北>,想要了解更多内容,大家可以看一看这个项目,希望大家帮忙给一个star,谢谢了! <面试指北>项目地址:http ...

  3. 面向面试题和实际使用谈promise

    "金三银四,金九银十",都是要收获的季节.面对各种面试题,各种概念.原理都要去记,挺枯燥的.本文是面向面试题和实际使用谈一下Promise. Promise是什么? Promise ...

  4. 大白话理解promise对象

    Promise  代表了未来某个将要发生的事件(通常是一个异步操作)  Promise 是异步编程的解决方案,能够简化多层回调嵌套,代表了未来某个将要发生的事件.Promise是一个构造函数,本身有a ...

  5. 45道Promise面试题

    来看看通过阅读本篇文章要点: Promise的几道基础题 Promise结合setTimeout Promise中的then.catch.finally Promise中的all和race async ...

  6. 字节跳动-前端面试题 Multi Promise Order

    字节跳动-前端面试题 Multi Promise Order Promise Order Async/Await async function async1 () { console.log('asy ...

  7. 前端面试题之手写promise

    前端面试题之Promise问题 前言 在我们日常开发中会遇到很多异步的情况,比如涉及到 网络请求(ajax,axios等),定时器这些,对于这些异步操作我们如果需要拿到他们操作后的结果,就需要使用到回 ...

  8. 一道关于Promise应用的面试题

    题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次:如何让三个灯不断交替重复亮灯?(用Promse实现) 三个亮灯函数已经存在: function red(){ console.log('red') ...

  9. 通过一道笔试题浅谈javascript中的promise对象

    因为前几天做了一个promise对象捕获错误的面试题目,所以这几天又重温了一下promise对象.现在借这道题来分享下一些很基础的知识点. 下面是一个面试题目,三个promise对象捕获错误的例子,返 ...

随机推荐

  1. sencha touch NavigationView

    NavigationView 是官方根据Container控件扩展而来的,由一个导航栏和一个card组成,具备导航和返回时自动销毁当前界面的功能,非常适合新手使用. 其中导航栏的代码如下: Ext.d ...

  2. Windows应急响应操作手册

    查看表征异常 系统卡慢.宕机.CPU和内存占用高.网络拥塞或断网.磁盘空余空间无理由大幅度缩小等,根据以上表征,可以初步猜测系统面临的问题. windows 下查看系统基本信息 PS C:\Users ...

  3. Git - 使用BitBucket和SourceTree进行源代码管理遇到POST git-receive-pack (chunked)

    我使用的是SourceTree Mac版,提交到BitBucket时出现 一直处于 POST git-receive-pack (chunked)  状态,经过百度,解决问题 在使用SourceTre ...

  4. yii---判断POST请求

    我们在进行数据的提交的时候,很多时候会判断请求状态来进行不同的选择.常见的就是判断POST以及GET的请求方式,下面是YII判断POST请求的代码示例: public function actionP ...

  5. CvArr* to cv::Mat 转换

    OpenCV中的CvArr*的定义的一个空类型的指针,可以转换为其派生类CvMat和IplImage,那么如何将其转化为cv::Mat呢,其实很简单,只需要一行代码即可: // CvArr *_img ...

  6. httpclient 请求 json 数据

    基于\httpcomponents-client-4.5.5需要引入相关jar包如下: 必须导入commons-logging-1.2.jar,否则会提示 json api接口地址: https:// ...

  7. POJ-2181 Jumping Cows(贪心)

    Jumping Cows Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 7329 Accepted: 4404 Descript ...

  8. VUE中父组件向子组件传递数据 props使用

    VUE中,子组件是不能直接访问父组件的数据(一般来说,当然如果你要破坏原则也是可以),如下< <body> <div id="fathercomponent" ...

  9. 【转】.NET 应用程序是怎么运行的

    原文:http://www.cnblogs.com/xishuai/p/mono-dotnetcore.html  .NET应用程序运行过程 C#程序运行过程 CLR结构

  10. HDU 6318 - Swaps and Inversions - [离散化+树状数组求逆序数][杭电2018多校赛2]

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=6318 Problem Description Long long ago, there was an ...