这篇文章发布于 2014年02月19日,星期三,17:03,归类于 JS API。 阅读 211073 次, 今日 32 次 71 条评论
by zhangxinxu from http://www.zhangxinxu.com
本文地址:http://www.zhangxinxu.com/wordpress/?p=3975
一、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的作用,一种“先知”的形式。好比上帝,已经在他的时间维度的一瞬间规划好了你的一生(因为他有先知的能力)(他无需跟着经历你的时间),虽然你自己依然感受到了时间,以及各种变数。
对于JS代码而言,我们就是上帝。我们能够预知代码的走向,并规划好代码的人生。
回到当下,正在电脑前的我们,想想你会经历的事情,可能就会是下面这样子——沿着时间线,一种事情连着另外一件事,每件事都有多种可能的发展:
这就好比JS世界中的异步操作,例如Ajax请求,现实世界中,大部分的操作结果都不是即时显现的(类似Ajax操作),如果用JS表示我们的一生,那就会是一个超级无限长的嵌套。
实际上,无限的嵌套更符合我们当下所感受的世界的理解。既然如此,那为何我们写代码的时候不喜欢一堆嵌套呢?
很简单,我们写代码的时候,是以上帝的身份来处理代码的,我们是造物主;而不是以代码中的角色身份。目前的JS异步回调就是按照代码视角——随着时间推移,代码中的人和物会经历什么什么。那如何变成先知视角呢?很简单,使用Promise! 而这,就是Promise出现的意义:“将异步操作转换成更符合先知视角的形式展现”!
二、Promise-更符合现实世界的直觉认识
我们之于代码为先知,如果我们知晓某一个现实世界故事发展走向,其实我们也是先知。因此,很多时候,除了自己正在经历的未知的经历,我们可以使用Promise将现实世界映射。
早在11年,我写过一篇名叫“关于一个JS功能实现的思维方式”文章,讲的就是如何站在代码的层面,脱离现实世界的思维方式,以空间换时间来更简单实现我们需要的效果。
Promise与之类似,也可以说是一种思维方式的转变,Promise似乎能更好地映射现实世界,使事情的发展更符合我们的直觉(看完本文就知道其含义了~~)。
讲一个现实世界的故事吧。
男神觉得时机成熟了,手捧99朵披着月季的玫瑰向女神求婚:
“小妞妞,嫁给我吧!我发誓,我会对你一辈子好的!”
“这个嘛,你先去问问我爸爸,我大伯以及我大姑的意思,他们全部都认可你,我再考虑考虑!对了,如果我爸没有答复,大伯他们肯定是不会同意的;如果大伯没有答复,大姑也是不会同意的。”
如果你是男神,你会怎么做?
我的第一反应(直觉)是这样的:
- 买些好烟好酒登门拜访岳父大人,恳请岳父大人把女儿许配给自己;1日后,得到岳父答复,如果同意,攻略大伯;如果不同意,继续攻略1次,要是还不行,失败告终;
- 同上攻略大伯;
- 买上等化妆品,然后同上攻略大姑;
- 拿着满满的offer拿下女神。
这是我们现实世界思维方式下的直觉反应,很简单的,也很好懂。
但是如果我们使用目前的JS实现,会是怎样?
因为得到长辈同意是需要等待的,而且一次只能攻略一个长辈,而且还能重复攻略。如果我们使用当下的JS去套用现实逻辑,可能就会这样子(无需细看):
男神.请求({ 姓名: "岳父", 成功: 男神.继续请求({ 姓名: "大伯", 成功: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: "求婚失败" }) }), 失败: 男神.继续请求({ 姓名: "大伯", 成功: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: "求婚失败" }) }), 失败: "求婚失败" }) }), 失败: 男神.请求({ 姓名: "岳父", 成功: 男神.继续请求({ 姓名: "大伯", 成功: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: "求婚失败" }) }), 失败: 男神.继续请求({ 姓名: "大伯", 成功: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: 男神.继续请求({ 姓名: "大姑", 成功: 男神.最终请求({ 姓名: "女神", 成功: "求婚成功", 失败: "求婚失败" }), 失败: "求婚失败" }) }), 失败: "求婚失败" }) }), 失败: "求婚失败" }) })
立马就晕了!
为何按照现实思维,JS呈现的会这么复杂呢?
- 代码需要预先设定好未来要发生的事情(先知视角);但是现实世界并不会如此(经历者视角);
- 代码中,空间是稀缺资源;而现实世界中,时间才是。
因此,在传统的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.
现实世界中,我们要讲一个故事,都是一瞬间在脑中知晓(没有异步,等待时间经历的过程),例如教授死了,炸鸡啤酒女会哭得死去活来。在写代码逻辑的时候,脑中的反应也是如此,一瞬间知晓逻辑变化,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的,是很赞的,也是希望能有不同的感性认知的!
感性这东西,就像发动机里的润滑油,寒冬里的温暖阳光,会让事情变得妙不可言。
欢迎交流,欢迎探讨。
相关文章
- http://www.sitepoint.com/overview-javascript-promises/
- http://www.html5rocks.com/zh/tutorials/es6/promises/
- http://www.sitepoint.com/deeper-dive-javascript-promises/
- http://www.sitepoint.com/six-things-might-know-promises/
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:http://www.zhangxinxu.com/wordpress/?p=3975
(本篇完)
- Promise.all、race和any方法都是什么意思? (0.950)
- 简单了解HTML5中的Web Notification桌面通知 (0.633)
- 使用AbortController abort中断原生fetch或axios请求 (0.633)
- 纯JS实现图像的人脸识别功能 (0.633)
- Object.is/===、数组at/直接索引、substring/slice的区别 (0.633)
- ES6模板字符串在HTML模板渲染中的应用 (0.367)
- HTMLUnknownElement与HTML5自定义元素的故事 (0.317)
- 简单了解ES6/ES2015 Symbol() 方法 (0.317)
- JS字符串补全方法padStart()和padEnd()简介 (0.317)
- 万岁,浏览器原生支持ES6 export和import模块啦! (0.317)
- 漫谈js自定义事件、DOM/伪DOM自定义事件 (RANDOM - 0.050)
屌
大神!
深入浅出,强!
看着张大神的教程长大的,哈哈哈
上升到哲学层面了。。。太厉害了
太棒了,真的太牛了,我是前端小白刚接触promise,一开始还蛮害怕,看完之后棒棒哒
张大神就是牛,前端先驱
我给满分
文章开始的‘唠嗑’,很符合我的心理历程。我刚刚克服‘恐惧感’要学promise,这儿就把我的心思说了出来。。。。
然而,这是2年之前的文章了。我刚到读到这儿。
好有才,通俗易懂!
看过的所有promise介绍里,这个是最易懂的!
确实写得很好,学习了
跪着看完+1。张大牛的视角特别前沿啊,那时出来没多久的技术,已经被揣摩的很透彻。膜拜!多多向大神学习~
求婚经过写得很好。学习了思考思维
看遍你文章啊哈哈最羡慕你媳妇:)
不错
真的很厉害,当看到是2014年写的时候,我开始跪着看了.. = =。。
多谢!
我同你一样,也是跪着看完的。
两年前呐,现在才看到,文章写的很好,看完确实没有恐惧了,也理解了~
说的很生动形象
通俗易懂,赞一个
从14年毕业到现在,很多时候都找到你的帖子,帮助挺大的。
我就居然读懂了…
哈哈 你的文章太风趣了
赞
咦?我一直以为 最近刚刚有一个硕士**被抓是你呢?好像还挂了。。原来不是你,那毕业照的跟你太像了
你这个代码为什么一直报错:Uncaught (in promise) undefined
先知思维还是当局者思维?
先知思维:假设条件都成立的一种思维方式 暴力清晰简单
当局者思维:假设各种条件,成立的或者不成立的,最后导出结果
早在11年…吓死本宝宝了
将一个抽象的Promise概念讲的十分透彻!多谢博主!
很好理解,发评论只是为了说一句话:图好评~
旭哥对Promise的理解很赞!核心就是当局者和旁观者的转换。
假如每个请求之间有上下文关系怎么办呢,
比如 request2, 依赖request1的返回值。
就是故事这么写, 请求完岳父,然后岳父告诉你去请求谁才能搞定。
自己已经搞定,文章写的非常好,点赞下。
很有禅理的解说,可以不看代码,读懂思想就行了,很赞!
华科的同学怎么都这么扎(you)眼(cai)。from a WHUer.
没promise,
我也一直都是这样写的呀
写的很赞,豁然开朗
很赞,先有感性认知才能不抗拒。promise
从编码视角解析promise确实让我开了眼界啊!
我觉得站在上帝视角看promise是先知,后续的步骤均已规划好。而站在经历者视角看promise是未知,所以需要为成功和失败准备处理方式。
另外异步模式会将顺序执行的动作的时间线拉得很长,而promise可以从代码的结构层面上“缩短”这份距离感,更接近人类的思维模式(任务+顺序,不含任务间的具体时间距离)。
不知道上述理解有咩有问题,呵呵。
写得非常好! 赞~~
扯个无关的,普罗米修斯是Prometheus 🙂
哈哈哈我也想说这个来着!
本王感脚,之所以命名Promise就是用其“承诺”的意思。
所谓承诺,就是先说出要做的事情(bind),然后再去实现(handler)它。这恰恰体现了绑定、触发执行的过程,而且是异步~
这样理解可以吗?
@Sigma 所谓理解,只要自己解释得通即可!
本人看了您的文章獲益啊~!
去搜一下JQUERY相關文章有以下發現:
http://api.jquery.com/promise/
http://api.jquery.com/jquery.when/
這些是不是都是PROMISE的SYNTAX呢~!?
感覺不太一樣~
promise.js 是不是和 es6 的 promise 有本质的区别啊,用promise.js的话,是不是 函数还是同步执行的,支持es6 的 promise 的解释器 是真正的异步。 能不能 这么理解??? 求大神指点哈。。。。
js从来都是异步的,只不过以前是基于callback的异步,现在加上了基于promise的异步的实现
这个栗子将 Promise 厉害
好赞,通俗易懂。期待其他几个es6特性文章哦~
本来还想建议一下贴的代码,没想到大神一下子就改过来了,给大神严谨认真的态度点个赞
你可真够花心思的,写了几天吧?
是啊,玉不琢不成器啊!
这期间估计看了不止一部电影了吧。
Promise也是近几年被说烂了的东西了,不过还是楼主的文字比较和我胃口!
每次来看网站都觉得很funny,趣味娱乐学习于一体,还是那句话,感谢分享~
回调地狱的解决方式……正在酝酿文章呢鑫前辈就出文了,赞