自己动手实现ES6 Promise

不管是标准还是实现,现在Javascript的重心都放在了async-await上,Promise怎么看都像过时的东西。而且支持Promise的库有一大堆,就算不需要这些库,今天的浏览器和Node.js也已经原生支持Promise了。在这种前提下,为什么还要自己去实现一个Promise呢?

ES7的async-await建立在Promise上

客观来讲,由于await本身的特点,将来JS把底层API良好封装之后,即使用户完全不知道Promise,在使用上也不会有啥问题~

然而ES7的Async-Await,和Promise并不是毫不相关的竞争对手。实际上await后面必须跟着一个Promise对象。所以深入理解Promise不会是毫无用处的事情。

Promise不需要编译器/解释器的支持

将来可能成为主流的async-await,以及曾经火过一把的generator + co,这些都是需要编译器或者解释器级别的支持才能使用。

而Promise,是完全可以利用语言已有特性,作为一个库来实现!即使在非常原始的JS运行环境,你也可以自己实现一个Promise,而不需要等待其他人的帮助。

Promise是语言无关的

Promise还是独立于语言的,如果你要给另外一种编程语言实现Promise,只要照葫芦画瓢就行了。

也就是说,掌握Promise的实现原理,是一种回报率非常高的通用型技能。而且只需要很少的投入,一百多行代码而已。(核心的其实只有几十行)

所以,让我们开始吧!

如何实现

对于Promise这种代码量不大,但是行为复杂的程序,最好的学习方法是直接看代码。只看别人的解释可能会弄得似懂非懂。

先放一个我自己的实现以供参考,建议还在网上搜一下其他人的实现。很多论坛里面都有人写简单的部分Promise实现,大都有借鉴价值。通过看不同人的写法,可以更容易理解其核心部分。我在实现自己的Promise时,就看过了很多片段代码,然后才慢慢知道该怎么做。

最核心的方法

最核心的一个方法是Promise.prototype.then,实现了它,也就实现了Promise的一大半。如果再把Promise.allPromise.race实现了,你基本上就实现了完整的Promise,因为剩下的,都是简单的封装而已。

then

每次调用then,你都在创建一个新的Promise对象。then就像一个锁链一样,将前后的两个Promise对象连接起来。

为了突出这一点,我在自己的实现里面,特意把逻辑代码外移,下面是代码片段

Promise.prototype.then = function(resolveFn, rejectFn) {
  var pP = this
  return new Promise((res, rej) => thenHandler(res, rej, resFn, rejFn, pP))
}

all

调用Promise.all,也会返回一个新的Promise对象,all后面的then,是挂在这个新的Promise对象上的。

Promise.all = function(promises) {
  checkArray(promises)
  return new Promise((res, rej) => handleAll(promises, res, rej))
}

pending, fulfilled, rejected

要理解Promise,另一个关键在于理解Promise的「状态」。一个Promise对象是有三种状态的, pendingfulfilledrejected。为什么要有状态?为了分情况处理。

先举一个例子,假如我定义一个Proimse,但是不给它绑定then

var x = myReadFile("/tmp/text.txt")

这条语句在运行的时候,那个文件的内容其实已经在某个时间点读出来了。一直缓存在某个地方。吃完饭我们再来运行:

x.then(console.log)
//> Promise { <pending> }
//  blahblah..., the content of /tmp/text.txt

数据全都出来了!因为then会对Promise对象的「状态」进行判断。如果是pending状态,就把将要运行的函数存到Promise对象的一个数组里面,如果是fulfilled状态,也就是那个Promise的resolve已经被运行了,那么就直接调用then传来的函数。

这就是状态存在的意义。

然后就可以直接看代码了,需要的就是适当的耐心,乐观的态度~

如果读者对Promise不了解,想知道它运行的一些特点,那么可以继续往下看。

Promise的运行机理

接下来,我们通过两个例子来探索Promise的运行过程,为了减少重复代码,我们先定义一个函数,这个函数会返回一个Promise对象,这就是一切的开端。

function myReadFile(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, (e, d) => e ? reject(e) : resolve(d.toString()))
  })
}

使用Promise,代码经常看上去会是这个样子的

myReadFile("theFirst.json")
.then(JSON.parse)
.then(fn1)
.then(fn2)
.then(fn3)
.catch(console.error.bind(console))

上面这个例子中,真正能够异步的,只有第一步而已。一旦那个resolve被调用,后面的一连串都会顺着执行。就像多米诺骨牌一样。

那么如果中间有另一个地方需要异步怎么办呢,比如我需要读取另外一个文件? 你只需要在某个传给then的函数里面,返回一个新的Promise对象就行。

myReadFile("theFirst.json")
.then(JSON.parse)
.then(fn1)
.then(d => myReadFile("theSecond.json"))
.then(JSON.parse)
.then(fn3)
.catch(console.error.bind(console))

上面这个例子中,fn3处理的是theSecond.json文件的内容。

这一点可能理解上有点别扭,大概需要实现了Promise之后,才能清楚其中的猫腻。

关于Promise,我能想到的需要注意的,暂时就这么多了。以后如果有新的点子,会继续放上来。

Promise的缺点

我对Promise非常喜爱,但是它的确有缺点,比如一些中间变量无法共用,我们拿同步例子来做个对比

var a = readFileSync("blahblah.txt")
var b = fn1(a)
var c = fn2(b)
var d = fn3(c)
console.log(a, b, c, d)

而使用Promise的异步则是这个情况

myReadFile("blahblah.txt")
.then(fn1)
.then(fn2)
.then(fn3)
.then(d => console.log(d))

其中fn1的返回值只有fn2能获取,fn2的返回值只有fn3能获取…… 如果需要像同步版本那样,获取所有中间值,就必须把它们存为全局或者上层闭包变量。

但是这个小小的缺点不太要紧。瑕不掩瑜,Promise彻底解决了callback hell,让我对Javascript另眼相看。

后记

随着对Promise使用时间的增长,我意识到了Promise的一些其他优点。它不仅仅是解决了回调的“代码金字塔”问题。应该说,回调带来的真正问题,并不是代码不停往右边延展,而是你不能以正常的「函数」概念来思考问题。

什么是函数?我记得以前数学老师向我们解释:函数就是一个工厂,你给一个毛胚进去,它变出一个产品。

当时我还没有编程的概念,当我学会编程之后,更加赞同那个朴实的比喻了。函数这东西,就是做转换,它不仅要有输入,还要有输出。

而回调,是“没有”输出的。

它当然有输出,只不过很别扭,因为它不是作为返回值呈现,而是通过你传给它的另一个输入(回调函数)来处理。是不是隐约找到当年C语言那堆库函数在你心中留下的伤疤。

Promise重新赋予了我们“正常”的函数,这是它更重要的意义。

原文:http://madmuggle.me/articles/ES6_Promise.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,301评论 5 22
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,352评论 0 19
  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,167评论 0 16
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,706评论 0 5
  • 00、前言Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区...
    夜幕小草阅读 2,128评论 0 12