the-understanding-of-promise

我对 Promise 的理解

今天在下班路上,突然想起 Promise,然后又有点兴致,就记录一下。
也没打算像大佬们动不动就我们一起写个 Promise A+,就简单谈谈关于自己对 Promise 的一些理解。

异步 / callback

我理解的异步操作,本质上就是一种等待通知的过程。
通知的接收者需要有某种东西来承载,我们的 callback 就是充当通知的接收者。
自己大学有玩过单片机,它们与单片机中的中断很相似,单片机接受到外部的中断信号,会跳到处理中断的内存地址上,运行中断函数的逻辑。

回调陷阱是自然的

Promise 的文章很多都会说到,它是为了解决回调陷阱(callback hell)的问题。
这没毛病,真的没毛病,但我就想说说自己的想法。
我认为,按照命令式的思路来编写程序,写出回调陷阱的代码是十分自然的。

举个例子,我要做三个异步操作 A B C,三个操作之间是有数据依赖的,就是 C 依赖于 B,B 又依赖与 A。在通知的角度看,C 需要等待 B 的通知才能进行,而 B 需要等待 A 的通知才能进行。
按照普通操作,我写出这样的代码。good,一个完美的回调陷阱。

1
2
3
4
5
6
7
8
A(/* callbackA */ () => {
B(/* callbackB */ () => {
C(/* callbackC */ () => {
// OK
})
})
})

我用伪代码写下,可以知道,其实 A 的结束就意味着 B 的开始,B 的结束就意味着 C 的开始。(结束不是语句结束,而是逻辑真正结束,开始与结束之间存在等待通知的过程)
一个 tab 代表,在一个封闭的上下文中的一套独立的操作(callback)。
看,这不是自然而然的回调陷阱吗,按照这种想法编写,肯定就会出现回调陷阱。

1
2
3
4
5
6
START A
(END A
START B)
(END B
START C)
END C

异步流程控制

你别说,其实回调陷阱式的 callback 调用也是一种解决问题的方法,只不过不符合所谓的“优雅”罢了。
不管是回调陷阱的调用还是 Promise,他们都是去处理一个问题,就是把异步操作按顺序调用,以解决各个操作之间数据依赖的问题。
很多说 Promise 的文章都有提到了队列。
队列这种数据结构十分适合用来处理按顺序处理的操作,Promise 用队列来管理各个回调,从而使各个操作可以按顺序进行。
再来说 A B C 三个异步操作,现在是 Promise 形式的操作,熟悉的感觉。

1
2
3
A()
.then(() => B())
.then(() => C());

嗯,优雅

其实每一个 then 都是一次把监听函数塞进队列的过程,其实就是先让监听函数和 THEN 排个队。

1
2
3
4
A Promise Queue
|-----------------
(A) (THEN) (THEN)
|-----------------

A 的内部操作 (B C 同理)

1
2
3
4
5
A() {
return new Promise((resolve, reject) => {
RA(/* callbackA */ () => resolve()); // RA 代表真正的 A 操作
});
}

等 A 的操作通知触发 Promise 内部的监听函数(通过 resolve),触发后就会调用一次 then 的回调函数,也就是运行 B。
此时 B 也会返回一个 Promise(详细的我不说,例如返回一个值也会被当作一个 resolved 的 Promise), 之后 A 的 Promise 得到此结果,则把 A 的监听器移走,把队列交接给 B 的 Promise.

1
2
3
4
A Promise Queue -> B Promise Queue
|----------------- |--------------
(A) (THEN) (THEN) (B) (THEN)
|----------------- |--------------

B 与 C 交接同理

1
2
3
4
A Promise Queue -> B Promise Queue -> C Promise Queue
|----------------- |-------------- |-----------------
(A) (THEN) (THEN) (B) (THEN) (C)
|----------------- |-------------- |-----------------

C 最后发现自己队列中空了,就代表操作完成

当然这是一种情况,会有队列花式合并的过程,比如 A 的 then 返回了一个 Promise 后面还加了一堆 then 的情况。

1
2
3
4
5
6
7
8
9
A Promise Queue
|-----------------
(A) (THEN) (THEN)
(THEN)
(THEN)
(THEN)
(THEN)
(THEN)
|-----------------

那队列交接时这个队列应该调整一下变成

1
2
3
4
5
6
7
8
9
A Promise Queue -> B Promise Queue
|----------------- |---------------------------------------------
(A) (THEN) (THEN) (B) (THEN) (THEN) (THEN) (THEN) (THEN) (THEN)
(THEN)
(THEN)
(THEN)
(THEN)
(THEN)
|----------------- |---------------------------------------------

Promise 就是用队列教会了我们怎么样好好的排队。

为了优雅就要增加复杂度。

最后

看得懂吗?我其实只是写给自己看的。

最近我一直觉得最好还是能够去理解技术背后的思想,或者是希望解决什么问题,而不仅仅它是怎么样的。
虽说这是老生常谈,但是自己好像就没有具体的感觉,今天不知道怎么的,突然有实感了,有那种问题出现了,而 Promise 正好能解决它的感觉,就简单地记下来。

关于排队

前排去日本,有个很冲击心灵的瞬间,在惠比寿的地下铁扶手电梯旁边,那天地铁口很多人。
东京的扶手电梯都是左立右行的,于是我看到一条延长到地铁站内的左立道,那队简直整齐得就像电梯的延长线。
这是在国内看惯扶梯口有一团人头的我所感觉到震惊的。。(⊙o⊙)
右边完全空出了一条行走通道,服了日本人准守规则的能力,不过也正是这种规则才能得到高效的通行效率。
在铁道博物馆,有部分电车驾驶舱参观需要排队,日本人带着他们的孩子过来玩,不时听到他们都会一直对小朋友说 “行列だよ”(要排队哦),这种习惯还真是从小养成的。