Promise详解(含面试题精华讲解)!!

这两天一直在看有关Promise的内容,但是不太确定怎样才叫做真正掌握,所以先把目前自己的理解写下来,总结一下。

文章最后的一小节【练习】我也加入了 很 多 遇到的有趣的promise面试题,大家顺便可以检测一下自己的知识是否巩固。

章节直通车:

概念

Promise是跟随es6出来对异步回调地狱的一种很好的解决方案,之前的很多异步回调是很难检测他报错的,所以就用Promise(承诺)这个单词来向广大程序员保证:“我可以做到”,紧接着风靡前端。(以上都是鬼扯)

Promise是异步的一种解决方案,他有三种状态:pending(等待)、fulfilled(完成)、rejected(失败)。

用法

基础用法

首先根据下面的代码自己脑补一下会打印什么:

new Promise(function(resolve, reject) {
  console.log(1);
  resolve('success');
  console.log(2);
  reject('error');
  console.log(3);
}).then(function(value) {
  console.log('then', value);
}).catch(function(err) {
  console.err('error:', err);
});

|

|

|

|

|

正确答案是

1
2
3
then success

我们根据上面的情况来具体聊聊Promise,

  1. Promise必须存在一种状态(pending或fulfilled或rejected)。如果为pending状态,那么可以转换到其他的两个状态;如果为fulfilled状态,那么必须有一个值,并且值和状态不可改变;如果为rejected状态,那么必须有一个原因,并且状态和原因不可改变。



上面的说法是人为约定俗成的(可参考Promise/A+规范),所以我们当我们看到 resolve('success');后面还有reject('error');,因为前者状态已经改变为fulfilled,所以状态不会改变,往后传入值success

  1. then方法接受两个参数,两个参数都需要为函数,否则会出现值穿透现象;then方法会继续返回一个新的Promise;then需要return一个值,如果没有return值,默认return的为undefined。

  2. 由于catch和then返回的都是一个新的Promise,所以他可以继续往后链式调用。

then(function(value) {
  console.log('then', value);
})

这里值传了一个函数,获取到的value是resolve传的字符串success。

  1. catch是语法糖,他其实相当于then(null, function failed() {})。

We have a problem with promises

之所以这个小节是英文名,是因为之前在Twitter上有一个很火的文章 —— 《We have a problem with promises》 原文翻译,推荐大家深入看看,这里我就把里面的四道题拿出来,我们继续探讨一下。

判断下面四段代码的区别:

声明:在这些例子中,假定 doSomething() 和 doSomethingElse() 均返回 promises,并且这些 promises 代表某些在 JavaScript event loop (如 IndexedDB, network, setTimeout) 之外的某些工作结束,这也是为何它们在某些时候表现起来像是并行执行的意义。

假设doSomething并没有返回值。

  1. 代码1
doSomething().then(function () {
  return doSomethingElse();
}).then(finalHandler);

这里的代码中规中矩,return了一个新的promise,所以后面的then都是基于doSomethingElse返回的promise往后继续执行的。

doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |------------------|
                                     finalHandler(resultOfDoSomethingElse)
                                     |------------------|
  1. 代码2
doSomething().then(function () {
  doSomethingElse();
}).then(finalHandler);
doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |--------------------------------|
                  finalHandler(undefined)
                  |------------------|

第一个then没有返回值,所以默认是返回undefined;

其次doSomethingElse()是一个异步操作,所以finalHandler会在doSomething执行完之后(中间执行的一些时间可以直接忽略掉)开始。

  1. 代码3
doSomething().then(doSomethingElse())
  .then(finalHandler);
doSomething
|-----------------|
doSomethingElse(resultOfDoSomething)
|--------------------------------|
                  finalHandler(undefined)
                  |------------------|

因为then传递的参数是一个自执行的函数,所以doSomething和doSomethingElse相当于同步执行,then默认传递undefined,所以等doSomething执行完后,finalHandler会继续执行。

  1. 代码4
doSomething().then(doSomethingElse)
  .then(finalHandler);

因为doSomethingElse本身是一个function,then本身是接受function的,所以这个和代码1(只是在外面又包了一层function)是相同的。

doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |------------------|
                                     finalHandler(resultOfDoSomethingElse)
                                     |------------------|

练习

练习1:

let a = Promise.reject('a').then(() => {
    console.log('a passed')
}).catch(() => {
    console.log('a failed')
});

Promise.reject('b').catch(() => {
    console.log('b failed');
}).then(() => {
    console.log('b passed');
});

|

|

|

|

|

正确答案:

b failed
a failed
b passed

原因:

如果认为描述的还不够清楚,可以参考这篇文章 讲解

  1. catch是其实就是then(null, function(){}),所以他也是一步一步串联着往后运行,并不会特殊的跳到后面;
  2. 涉及到了JavaScript的执行顺序问题:

(1)JavaScript分为宏任务和微任务(两者一般都为异步任务);特殊的,主线程也属于宏任务。

微任务有 Promise, I/O 等等
宏任务有 setTimeout, setInterval 等等

其中,宏任务和微任务是交替进行的,先是第一波宏任务和第一波微任务,然后再是第二波宏任务和微任务 ··· ···(按顺序then往下走,就可以分为第N波任务)

let a = Promise.reject('a')

相当于

let a = new Promise((resolve, reject) => {
  reject('a');
})

所以这个属于主线程,立即执行。

所以代码中宏任务和微任务可以按照下面分类和执行

先拆分

let p1 = Promise.reject('a').then(() => {
    console.log('a passed')
});

p1.catch(() => {
    console.log('a failed')
});

let p2 = Promise.reject('b').catch(() => {
    console.log('b failed');
});

p2.then(() => {
    console.log('b passed');
});

然后排序

// 一级微任务
let p1 = Promise.reject('a').then(() => {
    console.log('a passed')
});
let p2 = Promise.reject('b').catch(() => {
    console.log('b failed');
});

// 二级微任务
p1.catch(() => {
    console.log('a failed')
});
p2.then(() => {
    console.log('b passed');
});

所以按照顺序打印,由于p1抛出异常,紧跟着的是then不运行,p2抛出异常,紧跟着的是catch,打印b failed;由于没有宏任务所以直接运行二级微任务,p1找到了catch所以输出a failed;p2由于catch正常运行,返回了一个新的promise并且没有抛出异常,所以接着往下走,打印b passed。

练习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

原因:

其实我们在上一题解释过宏任务和微任务,这里我就直接拆分并且排序一下

// 主线程
const p1 = new Promise(function (resolve) {
  console.log(2);
  for (var i = 0; i < 100; i++) {
    i == 99 && resolve();
  }
  console.log(3);
});

console.log(5);

// 一级宏任务
setTimeout(function () {
  console.log(1);
}, 0)

// 一级微任务
p1.then(function () {
  console.log(4);
})

主线程 new Promise直接运行,打印2,3随后console打印5,由于主线程属于宏任务,所以接下来运行一级微任务打印4,最后运行一级宏任务,打印1。

练习3:

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

|

|

|

|

|

正确答案:

1
2

原因:

打印1的原因很简单,认为resolve了一个值为1,被下面的then接收到了,其次由于then返回的promise没有抛出错误,所以紧接着下面的catch没有运行,继续往下链式调用,打印了上一个then返回的值2。

练习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捕获错误了之后还是可以继续进行链式调用的。

这个题目的迷惑就是为什么then抛出了错误之后,没有被捕获到;原因是catch本身是可以捕获到的,可是在后面的catch没有处理错误,而是直接返回了1,所以后面就顺风顺水的走了两个then,打印了了一个2。

后面四个问题,如果你还是没有明白,可以参考文章: 后四个题解析

好啦,文章结束~欢迎大家指出错误!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349