疫情前去了两次日本,分别逛了 JR 东西两边的铁道博物馆,逐渐地喜欢上了日本的火车。特别是最后一次去高山旅游,在寒冷的天气里,运行时带着柴油发动机运转的气动车。 疫情这几年不少的日常消遣就是看拍车佬和铁路相关的 up 主视频,辞职空档期也趁国内疫情稳定和朋友出差的机会到西北,在国内跑了几趟非空调绿皮车,又爱上了火车旅行。 趁着 JR Pass 在 10 月要涨价之前,给自己安排了一个火车旅行,去几个日本农村走走。但最近东京电力的事情搞到舆论沸沸扬扬,恐怕要多费口舌才能得到家人的理解。
我用伪代码写下,可以知道,其实 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 的情况。