一、JavaScript 异步编程背景
从去年 ES2015 发布至今,已经过去了很久,ES2015 发布的新的语言特性中最为流行的也就莫过于 Promise 了,Promise 使得如今 JavaScript 异步编程如此轻松惬意,甚至慢慢遗忘了曾经那不堪回首的痛楚。
其实从 JavaScript 诞生,JavaScript 中的异步编程就已经出现,例如点击鼠标、敲击键盘这些事件的处理函数都是异步的,时间到了 2009 年,Node.js 横空出世,在整个 Node.js 的实现中,将回调模式的异步编程机制发挥的淋漓尽致,Node 的流行也是的越来越多的 JavaScripter 开始了异步编程,但是回调模式的副作用也慢慢展现在人们眼前,错误处理不够优雅以及嵌套回调带来的“回调地狱”。
这些副作用使得人们从回调模式的温柔乡中慢慢清醒过来,开始寻找更为优雅的异步编程模式,路漫漫其修远兮、吾将上下而求索。时间到了 2015 年,Promise 拯救那些苦苦探索的先驱。行使它历史使命的时代似乎已经到来。
每个事物的诞生有他的历史使命,更有其历史成因,促进其被那些探索的先驱们所发现。
了解 nodejs 或者熟悉浏览器的人都知道,JavaScript 引擎是基于事件循环或单线程这两个特性的。更为甚者在浏览器中,更新 UI(也就是浏览器重绘、重拍页面布局)和执行 JavaScript 代码也在一个单线程中,可想而知,一个线程就相当于只有一条马路,如果一辆马车抛锚在路上了阻塞了马路,那么别的马车也就拥堵在了那儿,这个单线程容易被阻塞是一个道理,单线程也只能允许某一时间点只能够执行一段代码。
同时,JavaScript 没有想它的哥哥姐姐们那么财大气粗,像 Java 或者 C++,一个线程不够,那么再加一个线程,这样就能够同时执行多段代码了,但是这样就会带来的隐患就是状态不容易维护,JavaScript 选择了单线程非阻塞式的方式,也就是异步编程的方式,就像上面的马车抛锚在了路上,那么把马车推到路边的维修站,让其他马车先过去,等马车修好了再回到马路上继续行驶,这就是单线程非阻塞方式。正如 Promise 的工作方式一样,通过 Promise 去向服务器发起一个请求,毕竟请求有网络开销,不可能马上就返回请求结果的,这个时候 Promise 就处于 pending 状态,但是其并不会阻塞其他代码的执行,当请求返回时,修改 Promise 状态为 fulfilled 或者 rejected(失败请求)。同时执行绑定到这两个状态上面的“处理函数”。这就是异步编程的模式,也就是 Promise 兢兢业业的工作方式,在下面一个部分将详细讨论 Promise。
二、Promise 基础
怎么一句话解释 Promise 呢?Promise 可以代指那些尚未完成的一些操作,但是其在未来的某个时间会返回某一特定的结果。
当创建一个 Promise 实例后,其代表一个未知的值,在将来的某个时间会返回一个成功的返回值,或者失败的返回值,我们可以为这些返回值添加处理函数,当值返回时,处理函数被调用。Promise 总是处于下面三种状态之一:
- pending: Promise 的初始状态,也就是未被 fulfilled 或者 rejected 的状态。
- fulfilled: 意味着 promise 代指的操作已经成功完成。
- rejected:意味着 promise 代指的操作由于某些原因失败。
一个处于 pending 状态的 promise 可能由于某个成功返回值而发展为 fulfilled 状态,也有可能因为某些错误而进入 rejected 状态,无论是进入 fulfilled 状态或者 rejected 状态,绑定到这两种状态上面的处理函数就会被执行。并且进入 fulfilled 或者 rejected 状态也就不能再返回 pending 状态了。
三、边学边写
上面说了那么多,其实都是铺垫。接下来我们就开始实现自己的 Promise 对象。go go go!!!
第一步:Promise 构造函数
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))
}
}
现在我们的代码就看上去简洁多了。
第二步:为 Promise 添加处理函数
其实通过 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 添加失败处理函数。
网友评论