ES6 JavaScript Promise的感性认知

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

 

一、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的,是很赞的,也是希望能有不同的感性认知的!

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

欢迎交流,欢迎探讨。

相关文章

(本篇完)

分享到:


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

  1. he说道:

    大神!

  2. soloyeah说道:

    深入浅出,强!

  3. 于梦中说道:

    看着张大神的教程长大的,哈哈哈

  4. 您的死忠粉说道:

    上升到哲学层面了。。。太厉害了

  5. 糖唐说道:

    太棒了,真的太牛了,我是前端小白刚接触promise,一开始还蛮害怕,看完之后棒棒哒

  6. 天涯过客说道:

    张大神就是牛,前端先驱

  7. 我给满分说道:

    我给满分

  8. songlairui说道:

    文章开始的‘唠嗑’,很符合我的心理历程。我刚刚克服‘恐惧感’要学promise,这儿就把我的心思说了出来。。。。
    然而,这是2年之前的文章了。我刚到读到这儿。

  9. semifi说道:

    好有才,通俗易懂!

  10. bruce说道:

    看过的所有promise介绍里,这个是最易懂的!

  11. 说道:

    确实写得很好,学习了

  12. sunp说道:

    跪着看完+1。张大牛的视角特别前沿啊,那时出来没多久的技术,已经被揣摩的很透彻。膜拜!多多向大神学习~

  13. 我是忠实粉丝说道:

    求婚经过写得很好。学习了思考思维

  14. fifi说道:

    看遍你文章啊哈哈最羡慕你媳妇:)

  15. 121说道:

    不错

  16. yuhui说道:

    真的很厉害,当看到是2014年写的时候,我开始跪着看了.. = =。。
    多谢!

  17. Miss Nine说道:

    两年前呐,现在才看到,文章写的很好,看完确实没有恐惧了,也理解了~

  18. 说道一下说道:

    说的很生动形象

  19. gyw说道:

    通俗易懂,赞一个

  20. 牙膏说道:

    从14年毕业到现在,很多时候都找到你的帖子,帮助挺大的。

  21. bmbx说道:

    我就居然读懂了…

  22. 王智智说道:

    哈哈 你的文章太风趣了

  23. 四眼猫说道:

  24. 呵呵说道:

    咦?我一直以为 最近刚刚有一个硕士**被抓是你呢?好像还挂了。。原来不是你,那毕业照的跟你太像了

  25. 过客说道:

    你这个代码为什么一直报错:Uncaught (in promise) undefined

  26. 天空很蓝说道:

    先知思维还是当局者思维?
    先知思维:假设条件都成立的一种思维方式 暴力清晰简单
    当局者思维:假设各种条件,成立的或者不成立的,最后导出结果

  27. 花天奇说道:

    早在11年…吓死本宝宝了

  28. jaingjunzhangaa说道:

    将一个抽象的Promise概念讲的十分透彻!多谢博主!

  29. xxmy说道:

    很好理解,发评论只是为了说一句话:图好评~

  30. 跑调的包子说道:

    旭哥对Promise的理解很赞!核心就是当局者和旁观者的转换。

  31. blessbygod说道:

    假如每个请求之间有上下文关系怎么办呢,

    比如 request2, 依赖request1的返回值。

    就是故事这么写, 请求完岳父,然后岳父告诉你去请求谁才能搞定。

  32. sandheart说道:

    很有禅理的解说,可以不看代码,读懂思想就行了,很赞!

  33. gnat说道:

    华科的同学怎么都这么扎(you)眼(cai)。from a WHUer.

  34. 业界大牛说道:

    没promise,
    我也一直都是这样写的呀

  35. penouc说道:

    写的很赞,豁然开朗

  36. 子非鱼说道:

    很赞,先有感性认知才能不抗拒。promise

  37. ^_^肥仔John说道:

    从编码视角解析promise确实让我开了眼界啊!
    我觉得站在上帝视角看promise是先知,后续的步骤均已规划好。而站在经历者视角看promise是未知,所以需要为成功和失败准备处理方式。
    另外异步模式会将顺序执行的动作的时间线拉得很长,而promise可以从代码的结构层面上“缩短”这份距离感,更接近人类的思维模式(任务+顺序,不含任务间的具体时间距离)。
    不知道上述理解有咩有问题,呵呵。

  38. riophae说道:

    写得非常好! 赞~~

  39. Johnny说道:

    扯个无关的,普罗米修斯是Prometheus 🙂

  40. Sigma说道:

    本王感脚,之所以命名Promise就是用其“承诺”的意思。

    所谓承诺,就是先说出要做的事情(bind),然后再去实现(handler)它。这恰恰体现了绑定、触发执行的过程,而且是异步~

    这样理解可以吗?

  41. Gazzooid说道:

    本人看了您的文章獲益啊~!

    去搜一下JQUERY相關文章有以下發現:

    http://api.jquery.com/promise/
    http://api.jquery.com/jquery.when/

    這些是不是都是PROMISE的SYNTAX呢~!?
    感覺不太一樣~

  42. peakfish说道:

    promise.js 是不是和 es6 的 promise 有本质的区别啊,用promise.js的话,是不是 函数还是同步执行的,支持es6 的 promise 的解释器 是真正的异步。 能不能 这么理解??? 求大神指点哈。。。。

    • jflame说道:

      js从来都是异步的,只不过以前是基于callback的异步,现在加上了基于promise的异步的实现

  43. peakfish说道:

    这个栗子将 Promise 厉害

  44. tq说道:

    好赞,通俗易懂。期待其他几个es6特性文章哦~

  45. 潇意说道:

    本来还想建议一下贴的代码,没想到大神一下子就改过来了,给大神严谨认真的态度点个赞

  46. Barret Lee说道:

    你可真够花心思的,写了几天吧?

  47. ck说道:

    Promise也是近几年被说烂了的东西了,不过还是楼主的文字比较和我胃口!

  48. 一枚蜗牛说道:

    每次来看网站都觉得很funny,趣味娱乐学习于一体,还是那句话,感谢分享~

  49. humphry说道:

    回调地狱的解决方式……正在酝酿文章呢鑫前辈就出文了,赞