前端异步编程的来龙去脉

这个异步的世界

很多其他语言的工程师,总是讥讽javascript中的异步编程。也有很多javascript工程师,缺乏道路自信,自甘堕落,在编程中以消灭异步模式为能事,异步的做法、异步的写法。

我们的异步编程,真的就矮人一等吗?回答不但是NO,我们还要指着这些人说:图样图森破。

平时,我们不是撸码,就是打游戏。有点钱的宅男宅女们,或许会炒股买比特币甘当韭菜,得了一种医学上被称为投资者幻觉的精神疾病。同学们,这是低级趣味,我们除眼前的苟且,还有诗和远方。让我们睁开眼睛看一看真实的世界吧。

比如,我是说比如,我老婆不在北京。在这里,大家可以自我代入一下,有老婆的想老婆,有老公的想老公。没有结婚的,或者隐婚一族,想一想你各位男朋友、女朋友。单身狗们嘛,YY一下好了,想一想白雪公主,或者那个骑白马的唐僧。

某一天夜里,电话铃突然响了。老婆说明天要来北京,核查一下我自己在北京有没有胡搞乱搞。

接到电话,正常的我们,大体上会有两种反应。JAVA工程师放下电话,会直奔车站,在出站口苶(nie)呆呆发愣,等着明天下午接站。我们javascript工程师当然不会这么二,我们会明天下午提前到出站口,老婆一出来就抱住他痛哭,倾诉衷肠。诉说我们如何昨天晚上一接到电话,就马上来车站等着他,觉也没有睡,饭也没有吃,水都没有喝一口,手机也没有带,朋友圈都没有刷。

当然,要说正常,那是作为一个工程师表现成这样是正常的。真实的我不会这样。注意,这里没有复数,是我,不是我们,因为我并不能代表在座诸位。

接完电话,我会面无表情的把电话装进裤兜里,继续灌满电热水壶烧水,水开了以后水壶会咔哒一声响。烧上水后,从冰箱里拿出一碗剩饭,放进微波炉热一下,热好了以后微波炉会嘀嘀响。完了以后把半个多月没有洗的衣服塞进洗衣机,自动洗好甩干放水,然后嘀嘀响。洗衣服的这工夫得擦擦地,擦完地吃饭,吃完饭喝一杯水睡觉。第二天闹钟响,起床,该干嘛干嘛,该上班上班。老婆车进北京,过了收费站会给我打电话,我就去车站。在附近随便逛逛,毕竟平时都见不到北京的太阳。出了检票口,我老婆会再给我打一个电话,我就慢吞吞的踱到出站口去接他。

当然,你可以说。第一种,同步的做法,表现出了一种痴情,一种童话般的爱情世界。第二种,异步的做法,粘呼呼的都是油腻,一股子庸俗的人间烟火气。但是,不管怎么说,作为一个中年油腻男,我把图样图森破这句话扔给你,你没有意见吧。

言归正传

我们已经认识到了,这个世界本来就是异步的。这个认识很重要。

我们在撸码的时候,最上乘的方法论,就是遵照这个世界自己本来的样子编程。所谓道法自然,如其本来。

那么好,我们言归正传,正式讨论编程中异步的处理。

在讨论异步编程的方案时,我们在这里只讨论方案的轮廓和概况,不讨论具体的设计细节和实现细节。因为这些细节 每一个都需要用一篇长文,或者几篇长文来处理,在我们这里装不下。

在JS中,如何进行异步编程?一些前端新秀,甚至一些后端老油条,都知道了ES6标准中有一个海藏神珍,叫Promise

对这样的人,我们要大声对他们说,同志哥,你out了。在日新月异的前端世界里,我们现在处理异步问题,已经使用ES7async-await方案了。

这个方案长成这个样子:

async function get(){
    let result = await fetch();
    console.log(result);
    return result;
}

这个方案似乎看上去消灭了异步,起码消灭了异步的写法。

但是这个写法,让很多略有经验的前端工程师疑窦丛生。疑问有二:

  • 这个变量赋值是异步的,还是同步的?
  • 在这个讨论的上下文中,大家很容易可以猜到是异步的。问题是,在这个同步的写法下,掩盖了什么细节,有什么坑没有?

经书有云,涉水道路漫水桥,虽有老司机,不敢径过。

先提出一个问题,留下伏笔。在这个方法的赋值过程中,javascript引擎可以不可以同时并发做其他事情?

我们用Promise方案重写一遍,如下:

function get(){
    return fetch().then((result) => {
        console.log(result);
        return result;
    });
}

用更老的callback方案重写一遍,如下:

function get(done){
    fetch(function(err, result){
        console.log(result);
        done(null, result);
    });
}

关于Promise方案和callback方案的进一步讨论,后文再行展开。

async-await方案的本质实现是Promise方案,只不过是javascript引擎替你做了这件事。

需要说明一下的是,async-await方案只是ES6Generator-yield方案的语法糖而已,本质上是一毛一样的。鉴于async只是取代了星号,await只是取代了yield,而且async-await方案可读性更好,这里就不再展开讨论Generator-yield方案。

我主张,前端新司机不急于先使用async-await方案,可以大量使用Promise方案。因为async-await在同步的形式下,掩盖了很多异步的内容,一不留神就会踩到坑、崴到脚。

异步是这个世界的本质,需要用心的去理解和体会。而名实相副,形式和内容保持某种一致的方案,显然更适合当新司机的教练车。

当然,有些峨冠博带奇装异服的老司机,注意,这里不是说生理年龄老,而是说心理年龄老,拢一拢没有几根的油腻头发,睁开满是眼屎的眼睛,睡眼惺忪的争辩道:“我们是年青人,要和最新的语言标准保持一致,要使用最前沿的技术方案”。

没错,async-await这种猥琐的解决方案,就是给你们这些猥琐的老司机提供的,大量使用就好了。总踩着油门也挺累的,定速巡航很不错,蛮好用。毕竟,你们这些老司机,即便开车睡着了,遇到异常时,也能在睡梦中一脚刹车踩死。

最后再看一组代码:

async function get(){
    try{
        let result1 = await fetch1();
        let result2 = await fetch2();
        let result3 = await fetch3();
        let result4 = await fetch4();
        let result5 = await fetch5();
        console.log(result1, result2, result3, result4, result5);
    }catch(ex){
    }
}

这组代码疑问有二:

  • 发生了异常怎么办?同步过程中、异步过程中的异常和回调中的异常地位相同吗?我想区分处理这三种异常怎么办?
  • 这几个方法是顺序依次执行的,没问题。问题是,很多时候,我们并不关心执行的顺序,反正都执行完了告诉我,我做我想做的事情就好。

如上代码处理异常是可以的,主要毛病还是同步的形式掩盖的异步的内容,新司机容易踩坑。比如,我不管发生了什么异常,总是执行某些代码,怎么办?当然可以用finally处理,问题是这个代码执行的时间点在什么时候?

使用Promise.all处理并发问题当然是可以的,但是Promise对象作为javascript王国中的当朝一品大员,难道就只是负责站在门口计数,等常委们到齐了喊主席来开会吗?这样的工作虽然挺重要,但一介哨兵足矣。

王国一品

javascript王国的一品大员有很多,这里的主角是Promise对象。所以,什么是Promise对象呢?

先看一下Promise对象的方法:

  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.prototype.then()
  • Promise.all()
  • Promise.race()
  • Promise.reject()
  • Promise.resolve()

其中,实例方法有3个,静态方法有4个。

Promise对象给一个定义很简单,网上多的是。我们主要从哪个特征是Promise对象的本质特征来看待这个问题,从这个角度来讨论。

什么是本质特征呢?就是说,一个对象具有某种特征,他就是Promise对象。当把这种特征抽离后,他就不称为Promise对象。我们认为,这种特征就是Promise对象的本质特征。

Promise对象又被称为thenable对象,就是遵循规范实现了then方法,是thenable接口的一个实例。也就是说,遵循规范实现了then方法,是Promise对象的本质特征。

在讨论Promise对象的本质特征时,我们有必要回顾一下十八层回调地狱。对于前端老帮菜来说,当然是一种缅怀和凭吊。对前端新司机来说,那就像在太湖上的小姑娘黄蓉一样,感慨一下国破家亡的故国情怀也不错。

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                step5(value4, function(value5) {
                    step6(value5, function(value6) {
                        step7(value6, function(value7) {
                            step8(value7, function(value8) {
                                step9(value8, function(value9) {
                                    step10(value9, function (value10) {
                                        step11(value10, function(value11) {
                                            step12(value11, function(value12) {
                                                step13(value12, function(value13) {
                                                    step14(value13, function(value14) {
                                                        step15(value14, function(value15) {
                                                            step16(value15, function(value16) {
                                                                step17(value16, function(value17) {
                                                                    step18(value17, function(value18) {
                                                                        // 地狱不空,誓不成佛
                                                                        console.log(value18);
                                                                    });
                                                                });
                                                            });
                                                        });
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    });
});

在历史上,有一位大菩萨,为了超度十八层回调地狱中的前端冤鬼,他说:我与你们立一个约。

处理异步问题时,大家无论什么时候都要遵守一个承诺。当正常执行异步任务后,把返回值传递给then方法的回调函数,任务失败后也要以类似的方式给予明确的回应。而且,一个异步任务要么是一个等待回应的状态,要么是成功,要么就是明确拒绝,此外不能有任何第三条道路可走。

在地狱中,有一只叫尾生的冤鬼说:南无大菩萨,弟子没听明白,求解释。

菩萨说,譬如娑婆世界无限有情众生,今天爱这个,明天爱那个,没有个定性。今天爱,明天不爱,反反覆覆。三角四角有很多,脚踩两只船的也不少。

我在这里定下一个《南海行为准则》。其略曰:别人向你求爱,你可以犹豫也可以考虑考虑。但是如果同意,要给人明确的答复。如果不同意,也要明确的拒绝。不要在那里态度不明半推半就,出了事情报异常。还有就是,同意了就同意,别回头又拒绝。拒绝了就不要再吃回头草,别来回反覆让人心神不宁。约会的时候,别人在等你,你要去就去,不想赴约的话,也给人一个明信。别你又不想去,让人在桥底下干等着。

尾生闻法欢喜,信受奉行。

在这里,有必要对Promise对象实例方法和静态方法作一个简要的说明。

实例方法,示例如下:

function get(){
    try{
        return fetch().then((result) => {
            console.log(result);
            return result;
        }).catch((err) => {
            console.error(err);
        }).finally(() => {
            // 同志,这是我的党费
        });
    }catch(ex){
        // 乍会走到这儿了呢?这不科学啊!
    }
}

静态方法,简要介绍:

  • Promise.resolve:遇到心仪的人表白,立马要答应,不想再考虑,但是又不能违了南海菩萨定下的行为准则。
  • Promise.reject:逻辑同上,只是立马要拒绝。
  • Promise.all:约几个朋友去K歌,不管先来后到,到齐了K起。到不了的也要电话确认下。
  • Promise.race:小朋友想出去玩,请示爸妈,有一个人同意就可以出去,有一个人拒绝就玩不成。SO,这个规则告诉我们,想出去玩的话,要先请示最可能同意的那个,想不出去玩的话,要先请求最可能拒绝的那个。当然爸妈里有一个嘴快的,这个策略就破功了。

没落的贵族

在异步编程中,回调函数无论如何都是要提及的。其一是因为他功勋卓著的历史地位,其二是因为在编程实务中依然扮演着的重要角色。Promise对象的then方法接受的参数本身就是一个回调函数。

回调函数在平时编程中用的很多,简单列举如下。

Promise对象的then方法中的回调:

fetch().then((data) => {
    console.log(data);
}).catch((err) => {
    console.error(err);
}).finally(() => {
    console.log('说完最后一句话');
});

Document事件回调:

document.querySelector('body').addEventListener('click', function(e){
    console.log(e);
});

jQuery ajax请求的回调

$.post('', function(res){
    console.log(res);
});

Node.js中的回调

fs.readFile('/etc/passwd', (err, data) => {
    if (err) throw err;
    console.log(data);
});

回调函数在javascript王国中的重要地位,是由函数在javascript王国中一等公民的地位决定的。

函数作为javascript语言中的灵魂,是可以脱离肉身而独立存在的。不像在一些弱鸡语言中,方法只能依附于对象存在,不是实例方法,就是静态方法。

我这么说,穿长衫的孔先生就会踱过来争辩,呃。。这个。。。我们强类型语言是世界上最好的语言,怎么能说是弱鸡语言呢?他还会继续说什么不变的形式具有西方逻辑美,等等,一些半懂不懂的话。

这个时候,我就会问他,是七十二般变化的孙悟空法术高强,还是你这种形式不变的肉眼凡胎法术高强?既然不变的形式在你西方逻辑中那么地位崇高,你西方哲学的起始为什么却是主张世界具有唯一本原的泰勒斯。然后,孔先生嘴里就会继续嘟囔着什么,回到工位埋头写BUG了。

具有七十二般变化的函数,是函数式编程的话题,在这个回调函数的主题里不展开讨论。

说到回调函数,有两个事情非常值得一提。

  • Node.js给出了回调函数约定,function(err, data){}。这个约定给异常以出口,是非常好的最佳实践。
  • 流程控制库Qasync,在属于他们的那个时代,一直都是最火的NPM包,可以很好的处理回调函数的依次执行、并发执行,等等流程控制的问题。现在回过头来看,没有ES标准中的方案那么简洁流畅,主要是还抱有彻底同步化的邪路思想,所谓炖鱼要炖出牛肉味来,比如异步的循环什么的。不过现在他都实现成了Promise

我们以倒叙的形式,讨论了前端异步编程的来龙去脉。在这里,具体的语法、具体的用法不是那么重要。最重要的是通过讨论,要确立一种信念,一种道路自信。这个世界本来就是异步的,异步编程是人间正道,既不是因循守旧的老路,也不是改旗易帜的邪路。

旁门源流

异步编程,从回调函数和流程管理的方案,发展到Promise对象和Generator-yield方案,再到ES7async-await方案,基本上是javascript中处理异步问题的正统源流。

除了这一支主流,还有一些旁门。菩提祖师曾对孙悟空说,三百六十旁门,皆可成正果。所以,有必要对异步处理的旁门做个简单介绍。

在这些旁门中,值得介绍的设计模式是观察者模式和中介者模式。常见伎俩如下:

  • 钩子回调,早期第三方库常用。常见的生命周期的概念,也可以称为钩子,只不过稍微复杂。
  • 事件回调,使用诸如addEventListeneron等方法注册回调函数,由dispatchEventemit等方法触发事件,向回调函数分发事件数据。
  • 发布订阅模式、观察者模式、中介者模式。

前端处理异步编程的这一支源流第三方库有很多,比如RxJS

参考链接

新时代的教育

作为投资的教育

教育是一种投资,这个观点基本上都是认可的。但是,要说教育是最好的投资标的,也许就不再能得到广泛的一般认可。

其实论证这个观点也简单。一笔够首付的钱,是拿去买房,还是投资教育,做一个简单的对比就可以证明。一个人,但凡有足够的提升空间,或者自己的子女有足够的成长进步空间,这笔钱不管是投资在自己的继续教育上,还是投资在子女的教育上,得到的投资回报显然都超过买房的回报,即便是在房子价格连年迅速增长,甚至翻番的时代也是如此。

这个论证的理论基础也很简单。房子虽然是优质资产,被人们乐于接受,甚至作为大资金的一般等价物,但毕竟只是死的资产,只是比其他死的资产更加优质而已。然而,教育投资的是劳动力。劳动是价值的源泉。劳动力在被教育投资后,可以源源不断的输出高附加值,是活的资产。房子作为优质资产,可以相对其它资产更增值。劳动作为价值的源泉,则相对一切资产增值,包括优质的房子。

既然是投资,就有不同标的的投资回报率问题,就像不同的房子有不同的回报率一样。

鉴于子女教育在教育中的重要地位,尤其是在家庭教育中的重要地位,后文提到的教育,如果没有特殊说明,就是指子女教育。

本文旨在进行能够提高教育投资回报率,或者选择投资回报率较高的教育标的的分析。

新时代思想指导下的教育

要想提高投资回报率,莫过于能够预知未来。在这里所做的分析,都是试图搞明白未来的教育是什么样的?其实,没有人能够真正的预知未来。我们所真正分析的,只是未来的教育应该是什么样的。

新时代思想被提出后,影响将是方方面面的,而且是极其深远的。

这一思想的科学性,并不是我们首先要讨论的。我们首先要讨论的是,这一思想能不能对教育产生深远的决定性影响。如果没有什么影响,我们当然就没有必要再对其进行郑重其事的分析。是什么决定影响力呢?我们假定是科学性。毕竟,相信真理的力量,是我们讨论问题的前提。

除了科学性,政治现实也是不容忽视的。作为一个执政党的指导思想,一个写入党章和宪法的思想,其影响必然是巨大的,尤其是党领导一切这一政治观点被贯彻后更是如此。

我们将试图解读,在新时代思想的指导下,未来的教育应该是什么样的?如果其具有科学性,我们就假定这是未来的教育。

当然,解读的准确性,以及这一思想在未来的影响,都是影响分析结果的重要因素。但是在这里,我们只能不得已的忽略这两个因素。

对重点教育的认知

集中力量,重点突破,是长期以来解决问题的一个重要思路,体现在教育上的就是重点教育。

重点突破,是一个时代的特征,满足了在资源匮乏的背景下取得成就的要求。比如。在经济治理上的,让一部分人先富起来。在体育上,争取那些重点培养运动员个人就可以取得奖牌。在教育上,广泛深入地开展重点教育,重点中学,重点大学,等等。

重点教育的特征,就是集中优秀的生源,集中优秀的师资力量,在培养人才上取得突破。

在进入新时代后,重点突破的思路在效率上钝化、在边际效用上递减。让一部分人先富起来,在这一部分人的财富以亿计的时候,这些人的财富再大量增加,哪怕是翻番再翻番,也不过是EXCEL表上的数字增加。对这些人的边际效用极大的趋近于零。这些增加的数字,在全社会的经济治理中效率钝化,也很难惠及广大的民生。在体育项目上,国家重点砸钱给个人运动员,就可以夺得金牌的那些项目,基本上全包了。即便是再砸更多的钱,也很难取得更多的金牌,只能追求体育场个人的更高更强。考试本来是测量的尺度,可以用来遴选人才。然而发展到了,为了进重点学校,杀鸡取卵涸泽而渔的压榨生源。这种对应试技术极致的挖掘,既违反了人性,也违背了教育的初心。总之一句话,就是重点突破的思路,几乎走到了尽头。

让人民普遍地受到教育,让人才有效的被遴选,是教育的初心。未来的教育,不再有重点学校。如果一定说有重点的话,那么所有的学校都是重点学校,所有的学校都将配备重点学校的硬件设施,所有的学校都将配备重点学校的师资力量。未来的教育,学校的重点痕迹将被取消或弱化,考试状元的称呼将被取消,对生源个人的应试压力将减轻。

山穷水尽疑无路,柳暗花明又一村。峰回路转,新时代的教育,为国家的教育资源打开了从点到面的投放空间,为教育事业提供了三五十年高速发展的可能。

教育中的家族竞争

所有的学校都是重点学校,在这种背景下,人才的遴选将更加苛刻。因为在这种背景下,具备优秀素质的生源大量涌现。

标准的重点教育场景是把各地优秀的尖子生汇聚一堂。在这种场景下,学生的学习积极性、学习氛围,都不是短板,老师只管认真的教学就好。在自我强化的索罗斯反身性作用下,优秀的硬件设施、优秀的师资力量都向这里汇集。这是重点教育的重要因素,但不是最重要的因素,更不是唯一的因素。蒸沙做饭,只得热沙。重点教育,最重要的因素是尖子生的汇聚。

大部分学生的可塑性很强,在好的学习氛围中学习成绩就好,在不好的学习氛围中学习成绩就不好。这样,重点教育的一个副影响就是,如果能够通过花钱,或者调动其他资源,把一个普通的孩子塞进重点学校,在良好的学习氛围下,就会产生好的学习成绩。当然,重点学校的优秀硬件设施和优秀师资力量的作用也是很大的。但是我们分析时,也把这一因素归入学习氛围因素中。

新时代思想指导下的教育,硬件设施趋同,师资力量趋同,尖子生散布在各学校,学习氛围就必然趋同。家庭的学习氛围,家族对教育的重视程度,将成为在人才遴选中胜出的决定性因素。在教育作为基础服务均等化的背景中,获取外部环境优势的代价将是高昂的,那种花一点钱就可以取得优势的时代一去不复返了。

教育作为基础服务的均等化

人民有普遍受到更好教育的权利,这是新时代崇高的教育理想。在这一理想的光辉指引下,12年级义务教育、高等教育、本科教育,必将得到普及。作为基础服务的教育机会将会均等化,机会的取得将是低成本的。

人才的遴选目标

除了崇高的教育理想,教育还有为国家、为社会遴选人才的职责。在新时代,人才遴选的目标是什么呢?一个人,出淤泥而不染,在一般化的学习氛围中,能够积极主动的学习,甚至通过自己影响力的作用,带动其他人认真学习。这样的人,放在任何地方,都会闪闪发光,这就是人才遴选的目标。当然这种主动,也许是出自对学习本身的热爱,也许是对学习改变自己命运的认知。

选择生源成为禁忌

选择各地尖子生作为生源,是新时代教育的禁忌,因为这是学校间的不公平竞争。学校需要研究的是,如何在无差别的生源条件下,取得教育成绩。当然,学校在提高了教育水平,增强了基础设施,汇集了更优秀的师资力量,能够提供更好的教育产品时,通过高收费教育选择愿意出钱的家庭生源,这是可以的。

选择师资力量的代价

除了家族的努力,能够选择较好的师资力量,较好的教育产品,是取得竞争优势的途径。但是在基础教育均等化的条件下,这种优势的取得代价是高昂的。当然,这也是家族竞争的一个方面。一笔同样的钱,同样的资源,边际效用较低的家庭会在这一竞争中胜出。

ES6 技术栈

文档

ES5 to ES6

构建工具 Gulp

构建工具 Rollup

ESLint

React

Airen(大漠)的博客

专题

动画

2016

2013

2011

参考

Tmux使用笔记

安装

sudo yum install tmux

基本使用

  • tmux:运行 tmux -2 以256终端运行
  • C-b d:返回主 shell , tmux 依旧在后台运行,里面的命令也保持运行状态
  • tmux ls:显示已有tmux会话(C-b s)
  • tmux a -t session-name:选择tmux
  • tmux new -s session-name:
  • tmux kill-session -t session-name:

快捷键

快捷键参考 按下 Ctrl-b 后的快捷键如下:

基础

  • ?:获取帮助信息

会话管理

  • s:列出所有会话
  • $:重命名当前的会话
  • d:断开当前的会话

管理窗口

  • c:创建一个新窗口
  • ,:重命名当前窗口
  • w:列出所有窗口
  • %:水平分割窗口
  • “:竖直分割窗口
  • n:选择下一个窗口
  • p:选择上一个窗口
  • 0~9:选择0~9对应的窗口

窗格管理

  • %:创建一个水平窗格
  • “:创建一个竖直窗格
  • q:显示窗格的编号
  • o:在窗格间切换

配置 ~/.tmux.conf

# bind a reload key
bind R source-file ~/.tmux.conf ; display-message "Config reloaded.."

教程