从去年ES2015发布至今,已通过去了一年多,ES2015发布的新的语言特性中最为流行的也就莫过于Promise了,Promise使得现在JavaScript异步编程如此轻松惬意,甚至慢慢遗忘了曾经那不堪回首的痛楚。其实从JavaScript诞生,JavaScript中的异步编程就已经出现,例如点击鼠标、敲击键盘这些事件的处理函数都是异步的,时间到了2009年,Node.js横空出世,在整个Node.js的实现中,将回调模式的异步编程机制发挥的淋漓尽致,Node的流行也是的愈来愈多的JavaScripter开始了异步编程,可是回调模式的反作用也慢慢展示在人们眼前,错误处理不够优雅以及嵌套回调带来的“回调地狱”。这些反作用使得人们从回调模式的温柔乡中慢慢清醒过来,开始寻找更为优雅的异步编程模式,路漫漫其修远兮、吾将上下而求索。时间到了2015年,Promise拯救那些苦苦探索的先驱。行使它历史使命的时代彷佛已经到来。javascript
每一个事物的诞生有他的历史使命,更有其历史成因,促进其被那些探索的先驱们所发现。了解nodejs或者熟悉浏览器的人都知道,JavaScript引擎是基于事件循环或单线程这两个特性的。更为甚者在浏览器中,更新UI(也就是浏览器重绘、重拍页面布局)和执行JavaScript代码也在一个单线程中,可想而知,一个线程就至关于只有一条马路,若是一辆马车抛锚在路上了阻塞了马路,那么别的马车也就拥堵在了那儿,这个单线程容易被阻塞是一个道理,单线程也只能容许某一时间点只可以执行一段代码。同时,JavaScript没有想它的哥哥姐姐们那么财大气粗,像Java或者C++,一个线程不够,那么再加一个线程,这样就可以同时执行多段代码了,可是这样就会带来的隐患就是状态不容易维护,JavaScript选择了单线程非阻塞式的方式,也就是异步编程的方式,就像上面的马车抛锚在了路上,那么把马车推到路边的维修站,让其余马车先过去,等马车修好了再回到马路上继续行驶,这就是单线程非阻塞方式。正如Promise的工做方式同样,经过Promise去向服务器发起一个请求,毕竟请求有网络开销,不可能立刻就返回请求结果的,这个时候Promise就处于pending状态,可是其并不会阻塞其余代码的执行,当请求返回时,修改Promise状态为fulfilled或者rejected(失败请求)。同时执行绑定到这两个状态上面的“处理函数”。这就是异步编程的模式,也就是Promise兢兢业业的工做方式,在下面一个部分将详细讨论Promise。java
怎么一句话解释Promise呢?Promise能够代指那些还没有完成的一些操做,可是其在将来的某个时间会返回某一特定的结果。 node
当建立一个Promise实例后,其表明一个未知的值,在未来的某个时间会返回一个成功的返回值,或者失败的返回值,咱们能够为这些返回值添加处理函数,当值返回时,处理函数被调用。Promise老是处于下面三种状态之一:git
pending: Promise的初始状态,也就是未被fulfilled或者rejected的状态。es6
fulfilled: 意味着promise代指的操做已经成功完成。github
rejected:意味着promise代指的操做因为某些缘由失败。编程
一个处于pending状态的promise可能因为某个成功返回值而发展为fulfilled状态,也有可能由于某些错误而进入rejected状态,不管是进入fulfilled状态或者rejected状态,绑定到这两种状态上面的处理函数就会被执行。而且进入fulfilled或者rejected状态也就不能再返回pending状态了。promise
上面说了那么多,其实都是铺垫。接下来咱们就开始实现本身的Promise对象。go go go!!!浏览器
Promise有三种状态,pending、fulfilled、rejected。缓存
const PENDING = 'PENDING' // Promise 的 初始状态 const FULFILLED = 'FULFILLED' // Promise 成功返回后的状态 const REJECTED = 'REJECTED' // Promise 失败后的状态
有了三种状态后,那么咱们怎么建立一个Promise实例呢?
const promise = new Promise(executor) // 建立Promise的语法
经过上面生成promise语法咱们知道,Promise实例是调用Promise构造函数经过new操做符生成的。这个构造函数咱们能够先这样写:
class Promise { constructor(executor) { this.status = PENDING // 建立一个promise时,首先进行状态初始化。pending this.result = undefined // result属性用来缓存promise的返回结果,能够是成功的返回结果,或失败的返回结果 } }
咱们能够看到上面构造函数接受的参数executor。它是一个函数,而且接受其余两个函数(resolve和reject)做为参数,当resolve函数调用后,promise的状态转化为fulfilled,而且执行成功返回的处理函数(不用着急后面会说到怎么添加处理函数)。当reject函数调用后,promise状态转化为rejected,而且执行失败返回的处理函数。
如今咱们的代码大概是这样的:
class Promise { constructor(executor) { this.status = PENDING this.result = undefined executor(data => resolveProvider(this, data), err => rejectProvider(this, err)) } } function resolveProvider(promise, data) { if (promise.status !== PENDING) return false promise.status = FULFILLED } function rejectProvider(promise, data) { if (promise.status !== PENDING) return false promise.status = FULFILLED }
Dont Repeat Yourselt!!!咱们能够看到上面代码后面两个函数基本相同,其实咱们能够把它整合成一个函数,在结合高阶函数的使用。
const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data } class Promise { constructor(executor) { this.status = PENDING this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } }
如今咱们的代码就看上去简洁多了。
其实经过 new Promise(executor)
已经能够生成一个Promise实例了,甚至咱们能够经过传递到executor中的resolve和reject方法来改变promise状态,可是!如今的promise依然没啥卵用!!!由于咱们并无给它添加成功和失败返回的处理函数。
首先咱们须要给咱们的promise增长两个属性,successListener和failureListener用来分别缓存成功处理函数和失败处理函数。
class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failureListener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } }
怎么添加处理函数呢?ECMASCRIPT标准中说到,咱们能够经过promise原型上面的then方法为promise添加成功处理函数和失败处理函数,能够经过catch方法为promise添加失败处理函数。
const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data switch(status) { case FULFILLED: return promise.successListener.forEach(fn => fn(data)) case REJECTED: return promise.failurelistener.forEach(fn => fn(data)) } } class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failurelistener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } /** * Promise原型上面的方法 */ then(...args) { switch (this.status) { case PENDING: { this.successListener.push(args[0]) this.failurelistener.push(args[1]) break } case FULFILLED: { args[0](this.result) break } case REJECTED: { args[1](this.result) } } } catch(arg) { return this.then(undefined, arg) } }
咱们如今的Promise基本初具雏形了。甚至能够运用到一些简单的场景中了。举个例子。
/*建立一个延时resolve的pormise*/ new Promise((resolve, reject) => {setTimeout(() => resolve(5), 2000)}).then(data => console.log(data)) // 5 /*建立一个及时resolve的promise*/ new Promise((resolve, reject) => resolve(5)).then(data => console.log(data)) // 5 /*链式调用then方法还不可以使用!*/ new Promise(resolve=> resolve(5)).then(data => data).then(data => console.log(data)) // Uncaught TypeError: Cannot read property 'then' of undefined
Promise须要实现链式调用,咱们须要再次回顾下then方法的定义:
then方法为pormise添加成功和失败的处理函数,同时then方法返回一个新的promise对象,这个新的promise对象resolve处理函数的返回值,或者当没有提供处理函数时直接resolve原始的值。
能够看出,promise可以链式调用归功于then方法返回一个全新的promise,而且resolve处理函数的返回值,固然,若是then方法的处理函数自己就返回一个promise,那么久不用咱们本身手动生成一个promise了。了解了这些,就开始动手写代码了。
const isPromise = object => object && object.then && typeof object.then === 'function' const noop = () => {} const statusProvider = (promise, status) => data => { // 同上面代码 } class Promise { constructor(executor) { // 同上面代码 } then(...args) { const child = new this.constructor(noop) const handler = fn => data => { if (typeof fn === 'function') { const result = fn(data) if (isPromise(result)) { Object.assign(child, result) } else { statusProvider(child, FULFILLED)(result) } } else if(!fn) { statusProvider(child, this.status)(data) } } switch (this.status) { case PENDING: { this.successListener.push(handler(args[0])) this.failurelistener.push(handler(args[1])) break } case FULFILLED: { handler(args[0])(this.result) break } case REJECTED: { handler(args[1])(this.result) break } } return child } catch(arg) { return this.then(undefined, arg) } }
首先咱们写了一个isPromise方法,用于判断一个对象是不是promise。就是判断对象是否有一个then
方法,免责声明为了实现上的简单,咱们不区分thenable和promise的区别,可是咱们应该是知道。全部的promise都是thenable的,而并非全部的thenable对象都是promise。(thenable对象是指带有一个then方法的对象,该then方法其实就是一个executor。)isPromise的做用就是用于判断then方法返回值是不是一个promise,若是是promise,就直接返回该promise,若是不是,就新生成一个promise并返回该promise。
因为须要链式调用,咱们对successListener和failureListener中处理函数进行了重写,并非直接push进去then方法接受的参数函数了,由于then方法须要返回一个promise,因此当then方法里面的处理函数被执行的同时,咱们也须要对then方法返回的这个promise进行处理,要么resolve,要么reject掉。固然,大部分状况都是须要resolve掉的,只有当then方法没有添加第二个参数函数,同时调用then方法的promise就是rejected的时候,才须要把then方法返回的pormise进行reject处理,也就是调用statusProvider(child, REJECTED)(data)
.
toy Promise实现的完整代码:
const PENDING = 'PENDING' // Promise 的 初始状态 const FULFILLED = 'FULFILLED' // Promise 成功返回后的状态 const REJECTED = 'REJECTED' // Promise 失败后的状态 const isPromise = object => object && object.then && typeof object.then === 'function' const noop = () => {} const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data switch(status) { case FULFILLED: return promise.successListener.forEach(fn => fn(data)) case REJECTED: return promise.failurelistener.forEach(fn => fn(data)) } } class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failurelistener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } /** * Promise原型上面的方法 */ then(...args) { const child = new this.constructor(noop) const handler = fn => data => { if (typeof fn === 'function') { const result = fn(data) if (isPromise(result)) { Object.assign(child, result) } else { statusProvider(child, FULFILLED)(result) } } else if(!fn) { statusProvider(child, this.status)(data) } } switch (this.status) { case PENDING: { this.successListener.push(handler(args[0])) this.failurelistener.push(handler(args[1])) break } case FULFILLED: { handler(args[0])(this.result) break } case REJECTED: { handler(args[1])(this.result) break } } return child } catch(arg) { return this.then(undefined, arg) } }
在ECMAScript标准中,Promise构造函数上面还提供了一些静态方法,好比Promise.resolve
、Promise.reject
、Promsie.all
、Promise.race
。当咱们有了上面的基础实现后,为咱们的toy Promise添加上面这些新的功能必定能让其更加实用。
在咱们的基本实现中,咱们并无区分thenable对象,其实Promise.resolve
和then
方法均可以接受一个thenable对象,并把该thenable对象转化为一个promise对象,若是想让咱们的toy Promise用于生产的话,这也是要考虑的。
为了让咱们的toy Promise变得更强壮,咱们须要拥有强健的错误处理机制,好比验证executor必须是一个函数、then方法的参数只能是函数或者undefined或null,又好比executor和then方法中抛出的错误并不可以被window.onerror监测到,而只可以经过错误处理函数来处理,这也是须要考虑的因素。
若是咱们的Promise polyfill是考虑支持多平台,那么首要考虑的就是浏览器环境或Node.js环境,其实在这两个平台,原生Promise都是支持两个事件的。就拿浏览器端举例:
unhandledrejection
: 在一个事件循环中,若是咱们没有对promise返回的错误进行处理,那么就会在window对象上面触发该事件。
rejectionhandled
:若是在一个事件循环后,咱们才去对promise返回的错误进行处理,那么就会在window对象上面监听到此事件。
关于这两个事件以及node.js平台上面相似的事件请参考Nicholas C. Zakas新书<understanding es6>
Promise可以很棒的处理异步编程,要想学好它我认为最好的方法就是亲自动手去实现一个本身的Promise,下面的项目Jocs/promise是个人实现,欢迎你们pr和star。