ES6 JavaScript Promise的感性认知

这篇文章发布于 2014年02月19日,星期三,17:03,归类于 JS API。 阅读 162902 次, 今日 37 次 68 条评论

 

一、Promise是一种形式

春节假期看了一部电影,《超人-钢铁之躯》,我媳妇没睡着,说明这部电影还不错。其中有句台词印象深刻:“对于没有见过的东西,人会排斥,因为内心会恐惧”。

于是,就有这样一个问题出来了,如果让人快速接受一个新鲜未知、可能会排斥、带来恐惧的东西。

“介绍其强大以及厉害之处?”

比如本文要介绍的Promise, 介绍其如何使用,多么强大?

No, No, No, 这一上来就直奔菊花的节奏显然是会带来痛苦的。

好比超人哥哥,如果对民众大肆宣扬其多么强大——揉不扁、锤不烂,上天入地是小菜。不怕枪、不怕弹,眼睛镭射狂点赞!

民众只会害怕,排斥,认为这是一个怪物。为何?因为缺乏完整的感性认识。相比强大与否,是敌是友,是好事是坏才是让民众接受的最先考虑的,恰好,此时发生了一连串的实际,超人哥哥以行动表明了自己是站在地球这一边的。

于是,美利坚才会如此迅速认可这位超人哥哥。

我们再来看Promise, 对于部分人而言,接触过类似概念或者有类似需求,本身就有感性认识,因此,此物出现的时候,好比久旱逢甘雨,干柴遇烈火,立马high起啊~

但是,大部分的人是第一次触碰Promise的肌肤。因此,在深入介绍之前,感性认识就尤为重要,否则,会害怕,排斥,扔在一边,等着以后吃现成饭。

我们看电影、电视剧时候都会关注剧情简介;授课或演讲时候会先说个大纲。这些其实都是先塑造感性认识,说白了,就是“脑补形象”。

所以,本文不会详细、完整介绍Promise的使用、特点等。这些您可以参考本文末尾留下的相关文章地址。只会让你对Promise有个感性认知,在脑中塑造出一个物体形象,或大叔、或萝莉、或御姐、或多来A梦、或**棒等。这样,才会有兴趣、才会更快的接受相关技术点、功能、使用技巧等。

OK,首先第1点,Promise是一种形式

注意这里的措辞,“Promise是一种形式”,也就是不要把Promise看成是某些方法、函数之类的。类似于不要把“小姐”看成某个人,而是某种职业。

Promise中文有“誓言”、“承诺”之意。从字面意思我们也似乎体会出一点“形式”的味道。但是,注意这里的转折,根据自己的理解,Promise完全不能按照“承诺”来理解,而是要根据其音译——“普罗米修斯”。普罗米修斯是希腊神话故事中的英雄,名字的意思是“先知”。Promise最初的命名是Futures, “未来”,可见Promise含义不是字面的“誓言”“承诺”之类,而是“先知”“未来”的意思。

那该如何理解“先知”,JS与先知有什么关系?

我们都知道,JS是单线程的,也就是一条线下来的,这是代码世界的一条规则。那么现实世界有与之呼应的吗?——人?No, No, 人是多线程的,人可以一边看动作片,一边撸智深。那是什么东西呢?哈,没错,时间,现实世界里,时间这条线就是单线程的,不会出现两条时间线,这种事是会出现在科幻故事里。

人当下所经历的现实世界中,很多行为结果是未知的,不是即时呈现的。例如,今天的NFL超级碗,巨星曼宁率领的野马队与本赛季黑马海鹰队的比赛。这实际上是个异步的过程,某明星压了1000万赌注野马队赢,比赛的结果会影响这位明星状态。这就类似于JS中发送一个异步的Ajax请求,不同的返回值造成不同的影响。

而Promise则扮演了一个“先知”的角色。预先将你的未来告知,规划好你继续的路。你无需等待最终结果出来,你可以继续你现在的生活。用一个简单的图表示就是下面这样:
Promise与时间线

如果你是一个先知,你是没有等待、异步这种感觉的。这就是Promise的作用,一种“先知”的形式。好比上帝,已经在他的时间维度的一瞬间规划好了你的一生(因为他有先知的能力)(他无需跟着经历你的时间),虽然你自己依然感受到了时间,以及各种变数。

对于JS代码而言,我们就是上帝。我们能够预知代码的走向,并规划好代码的人生。

回到当下,正在电脑前的我们,想想你会经历的事情,可能就会是下面这样子——沿着时间线,一种事情连着另外一件事,每件事都有多种可能的发展:
时间线与发展

这就好比JS世界中的异步操作,例如Ajax请求,现实世界中,大部分的操作结果都不是即时显现的(类似Ajax操作),如果用JS表示我们的一生,那就会是一个超级无限长的嵌套。

实际上,无限的嵌套更符合我们当下所感受的世界的理解。既然如此,那为何我们写代码的时候不喜欢一堆嵌套呢?

很简单,我们写代码的时候,是以上帝的身份来处理代码的,我们是造物主;而不是以代码中的角色身份。目前的JS异步回调就是按照代码视角——随着时间推移,代码中的人和物会经历什么什么。那如何变成先知视角呢?很简单,使用Promise! 而这,就是Promise出现的意义:“将异步操作转换成更符合先知视角的形式展现”!

二、Promise-更符合现实世界的直觉认识

我们之于代码为先知,如果我们知晓某一个现实世界故事发展走向,其实我们也是先知。因此,很多时候,除了自己正在经历的未知的经历,我们可以使用Promise将现实世界映射。

早在11年,我写过一篇名叫“关于一个JS功能实现的思维方式”文章,讲的就是如何站在代码的层面,脱离现实世界的思维方式,以空间换时间来更简单实现我们需要的效果。

Promise与之类似,也可以说是一种思维方式的转变,Promise似乎能更好地映射现实世界,使事情的发展更符合我们的直觉(看完本文就知道其含义了~~)。

插画-代码世界与现实世界

讲一个现实世界的故事吧。

男神觉得时机成熟了,手捧99朵披着月季的玫瑰向女神求婚:

“小妞妞,嫁给我吧!我发誓,我会对你一辈子好的!”
“这个嘛,你先去问问我爸爸,我大伯以及我大姑的意思,他们全部都认可你,我再考虑考虑!对了,如果我爸没有答复,大伯他们肯定是不会同意的;如果大伯没有答复,大姑也是不会同意的。”

如果你是男神,你会怎么做?

我的第一反应(直觉)是这样的:

  1. 买些好烟好酒登门拜访岳父大人,恳请岳父大人把女儿许配给自己;1日后,得到岳父答复,如果同意,攻略大伯;如果不同意,继续攻略1次,要是还不行,失败告终;
  2. 同上攻略大伯;
  3. 买上等化妆品,然后同上攻略大姑;
  4. 拿着满满的offer拿下女神。

这是我们现实世界思维方式下的直觉反应,很简单的,也很好懂。

但是如果我们使用目前的JS实现,会是怎样?

因为得到长辈同意是需要等待的,而且一次只能攻略一个长辈,而且还能重复攻略。如果我们使用当下的JS去套用现实逻辑,可能就会这样子(无需细看):

男神.请求({
   姓名: "岳父",
   成功: 男神.继续请求({
       姓名: "大伯",
       成功: 男神.继续请求({
           姓名: "大姑",
           成功: 男神.最终请求({
               姓名: "女神",
               成功: "求婚成功",
               失败: "求婚失败"
            }),
            失败: 男神.继续请求({
               姓名: "大姑",
               成功: 男神.最终请求({
                   姓名: "女神",
                   成功: "求婚成功",
                   失败: "求婚失败"
                }),
                失败: "求婚失败"
            })
        }),
        失败: 男神.继续请求({
           姓名: "大伯",
           成功: 男神.继续请求({
               姓名: "大姑",
               成功: 男神.最终请求({
                   姓名: "女神",
                   成功: "求婚成功",
                   失败: "求婚失败"
                }),
                失败: 男神.继续请求({
                   姓名: "大姑",
                   成功: 男神.最终请求({
                       姓名: "女神",
                       成功: "求婚成功",
                       失败: "求婚失败"
                    }),
                    失败: "求婚失败"
                })
            }),
            失败: "求婚失败"
        })
    }),
    失败: 男神.请求({
       姓名: "岳父",
       成功: 男神.继续请求({
           姓名: "大伯",
           成功: 男神.继续请求({
               姓名: "大姑",
               成功: 男神.最终请求({
                   姓名: "女神",
                   成功: "求婚成功",
                   失败: "求婚失败"
                }),
                失败: 男神.继续请求({
                   姓名: "大姑",
                   成功: 男神.最终请求({
                       姓名: "女神",
                       成功: "求婚成功",
                       失败: "求婚失败"
                    }),
                    失败: "求婚失败"
                })
            }),
            失败: 男神.继续请求({
               姓名: "大伯",
               成功: 男神.继续请求({
                   姓名: "大姑",
                   成功: 男神.最终请求({
                       姓名: "女神",
                       成功: "求婚成功",
                       失败: "求婚失败"
                    }),
                    失败: 男神.继续请求({
                       姓名: "大姑",
                       成功: 男神.最终请求({
                           姓名: "女神",
                           成功: "求婚成功",
                           失败: "求婚失败"
                        }),
                        失败: "求婚失败"
                    })
                }),
                失败: "求婚失败"
            })
        }),
        失败: "求婚失败"
    })
})

立马就晕了!

为何按照现实思维,JS呈现的会这么复杂呢?

  1. 代码需要预先设定好未来要发生的事情(先知视角);但是现实世界并不会如此(经历者视角);
  2. 代码中,空间是稀缺资源;而现实世界中,时间才是。

因此,在传统的JS中,类似这种多嵌套,多异步,多发展的,我们需要牺牲(对于程序来讲)微不足道的时间换取代码的空间和易维护性,于是,我可能就会这么实现(也无需细看):

var NanShen = {
    "身高": 180,
    "体重": 80,
    "年薪": "200K",
    request: function(obj) {
        // 攻略长辈成功与否随机决定
        // 成功概率为80%
        if (Math.random() > 0.2) {
            obj.success();
        } else {
            obj.error();
        }
    }
};

var Request = function(names, success) {
    var index = 0, first = 0;
    var request = function() {
        if (names[index]) {
            NanShen.request({
                name: names[index],
                success: function() {
                    first = 0;
                    console.log("成功拿下" + names[index]);
                    index++;
                    request();
                },
                error: function() {
                    if (first == 1) {
                        console.log("依旧没能拿下" + names[index] + ",求婚失败");    
                        return;
                    } else {
                        console.log("没能拿下" + names[index] + ",再试一次");    
                    }
                    first = 1;
                    request();    
                }
            });    
        } else {
            success();
        }
    };    
    
    request();
};

Request(["岳父", "大伯", "大姑"], function() {
    NanShen.request({
        name: "女神",
        success: function() {
            console.log("女神同意,求婚成功!");
        },
        error: function() {
            console.log("女神不同意,求婚失败!");
        }
    });
});

上面代码通过异步回调,有效重复利用了JS代码,实现了男神的求婚历程。

您可以狠狠地点击这里:传统JS思维下实现的求婚历程表现

男神的每次攻略成功率是80%, 因此,其攻略结果可能是这样:
求婚攻略结果

或者这样:
求婚攻略结果

或者这样:
求婚攻略结果

等。自己进入demo死命刷新测试自己人品吧~

————–低调的分隔线————-

不能因为本文主角是Promise就厚此薄彼,上面的JS实现是很赞的,以一种带有Promise的味道将多条件多异步的复杂世界用JS呈现出来,也是一种很巧妙的形式转化,套个噱头应该可以叫做“xxx模式”,呵呵~

然后,上面方法,依次执行的触发依然在回调中,其实并不符合我们现实的思考。我们可能希望得到的代码具有如下与现实世界统一的思维:“搞定岳父→搞定大伯→搞定大姑→搞定女神”,但是,上面的实现却看不出这样的思维。而Promise这种形式可以让代码呈现更符合真实世界的直觉。在这个故事里,女神其实已经把故事的走向全部告诉你了,这种情况下,我们其实已经知晓了未来,我们已经是先知,显然,使用Promise是很可行的。

那使用Promise该是什么模样呢?可参考下面的代码以及demo.

// 男神的各项参数
var NanShen = {
    "身高": 180,
    "体重": 80,
    "年薪": "200K",
    request: function(obj) {
        // 成功与否随机决定
        // 执行成功的概率为80%
        if (Math.random() > 0.2) {
            obj.success();
        } else {
            obj.error();
        }
    }
};

var Request = function(name) {
    return new Promise(function(resolve, reject) {
        var failed = 0, request = function() {            
            NanShen.request({
                name: name,
                success: function() {
                    console.log(name + "攻略成功!");
                    failed = 0;
                    resolve();
                },
                error: function() {
                    if (failed == 0) {
                        console.log("第一次攻略" + name + "失败,重试一次!");
                        failed = 1;
                        // 重新攻略一次
                        request();                       
                    } else {
                        console.log("依然没有拿下" + name + ",求婚失败!");
                        reject();
                    }
                }
            });
        };
		
        request();
    });
};

Request("岳父")                                // 搞定岳父,然后...
.then(function() { return Request("大伯"); })  // 搞定大伯,然后...
.then(function() { return Request("大姑"); })  // 搞定大姑,然后...
.then(function() {                            // 长辈们全部KO后,攻略女神
    NanShen.request({
        name: "女神",
        success: function() {
            console.log("女神同意,求婚成功!");
        },
        error: function() {
            console.log("女神不同意,求婚失败!");
        }
    });
});

代码没必要细看。等以后Promise规范成熟之后再细细研究。

哈,忘记demo了,您可以狠狠地点击这里:男神求婚历程的Promise实现

一样的风格,一样的味道,一样的随机攻略结果,例如像下面这样死在大伯手上:
被大伯给阻拦了,求婚失败

虽然代码量和传统JS实现不分伯仲,但是,注意这里,下面截图所示这圈代码:

Promise方式实现代码层面优势的解读

截图上的文字是:

对于先知的我们,故事的安排在脑中是即时出现的,而非实际上的异步发生。因此,作为创世的编程者而言,这种与瞬间思考所同步的代码更符合现实世界下的思考方式。因此,更易读更易于理解。

无论是代码世界中的编程还是现实世界的讲故事,我们都是一个先知的角色(除非你能让机器自我学习,无法预知其走向),都是Promise.

现实世界中,我们要讲一个故事,都是一瞬间在脑中知晓(没有异步,等待时间经历的过程),例如教授死了,炸鸡啤酒女会哭得死去活来。在写代码逻辑的时候,脑中的反应也是如此,一瞬间知晓逻辑变化,if(Jiaoshou == "dead") { crying~ }, 是先知。因此,我们更乐意看到符合我们瞬时思维的代码呈现,而不是符合实际发展的代码呈现。

先知思维的代码呈现就是Promise形式;当局者思维的代码呈现就是异步回调形式。

如果套用求婚的故事,则:

  • 先知思维(第三人称视角)——搞定岳父→搞定大伯→搞定大姑→搞定女神
  • 当局者思维(第一人称视角)——去搞定岳父→…等待期…结果来了…→去搞定大伯→…等待期…结果来了…→去搞定大姑→…等待期…结果来了…→去搞定女神→…等待期…结果来了…

前者是先知是上帝,在规划;后者是凡人,在经历。

Codeing以及讲故事都是规划,因此,Promise这种先知形式会让代码更符合我们的认知。说白了就是更好懂,更好维护,可以怎么想就怎么写,而无需写着写着发现丫的需要嵌套需要再套一个函数做回调。

三、结语以及Promise其他相关

ES6中除了Promise, 还有其他一些高级的东西,例如Generator, let, for-of. 不过先暂时放在一边,回头再掌握。

还是那句话,Promise只是让我们当下的JS换了一种形式实现,更符合编程时候的思考角度与方式。因此,通过特定的转换,我们也可以让不支持Promise的低版本浏览器支持之。您可以试试在页面引入如下JS:

<script src="http://s3.amazonaws.com/es6-promises/promise-0.1.1.min.js"></script>

GitHub点击这里

这也是Promise有众多关注的原因之一。

人总是有差异的。看到一件事物总是会有不同的认知的。例如,看到一幅盛开的菊花的图片,有人会认为这是菊花,还有人会这是“菊花”。对于Promise也是如此。但是,无论你怎么思考,符合你自己的思维方式,适合你自己内心世界的都是正确的认知。So, 又会被人批评啰嗦的本文所阐述的一些感性认知,如果您有不同的看法,有不同的形象认知,都是OK的,是很赞的,也是希望能有不同的感性认知的!

感性这东西,就像发动机里的润滑油,寒冬里的温暖阳光,会让事情变得妙不可言。

欢迎交流,欢迎探讨。

相关文章

(本篇完)

分享到:1
×


发表评论(目前68 条评论)

  1. 呂小成说道:

    鑫哥威武

  2. ok说道:

    传统求婚历程是异步吗?? 你是不是把异步理解错了。 明明全部是同步代码

  3. w说道:

    一句话就能概括的东西,东拉西扯这么多

  4. 啥地方说道:

    博主厉害了,一看就懂

  5. 321说道:

    鑫哥厉害呀

  6. xxoo说道:

    写的不错看了就能硬的博客!!!!

  7. 我是一个菠菜说道:

    感性这东西,就像发动机里的润滑油,寒冬里的温暖阳光,会让事情变得妙不可言。
    楼主对事物的理解能力,以及联想力都很棒~

  8. 菱薇说道:

    看了文章瞬间就明白了,简单易懂,给旭神赞一个

  9. 佩服说道:

    楼主写的非常好, 做为一个技术流, 文笔也这么好, 非常好

  10. 小溪河流说道:

    写的非常非常具体,太感谢了,楼主辛苦了

  11. 小溪河流说道:

    这样的故事形式讲解也是太牛逼了