首发于:sau交流学习社区
一、前言
什么是promise?promsie的核心是什么?promise如何解决回调地狱的?等问题
1、什么是promise?promise是表示异步操作的最终结果;可以用来解决回调地狱和并发IO操作的问题
A promise represents the eventual result of an asynchronous operation.
2、promise 的核心是什么?promise的核心就是链式调用
3、采用什么方法可以实现链式调用?通过使用then的方法,then方法是用来注册在这个Promise状态确定后的回调,很明显,then方法需要写在原型链上。
4、promise是如何解决回调地狱的问题?(1)如果一个promise返回的是一个promise,会把这个promise传递结果传递到下一次的then中;(2)如果一个promise返回的是一个普通的值,会把这个普通值作为下一次then的成功回调结果;(3)如果当前promise失败了,会走下一个then的回调函数;(4)如果then不返回值,就会有一个默认值为undefined,作为普通值,会作为下一个then的成功回调;(5)catch是错误没有处理的情况才会执行;(6)then中可以不写东西
二、promise的标准解读
1、只有一个then
方法,没有catch
,race
,all
等方法,甚至没有构造函数;
Promise标准中仅指定了Promise对象的then
方法的行为,其它一切我们常见的方法/函数都并没有指定,包括catch
,race
,all
等常用方法,甚至也没有指定该如何构造出一个Promise对象,另外then也没有一般实现中(Q, $q等)所支持的第三个参数,一般称onProgress
2、then
方法返回一个新的Promise;
Promise的then
方法返回一个新的Promise,而不是返回this,此处在下文会有更多解释
promise2 = promise1.then(alert)
promise2 != promise1 // true
3、不同Promise的实现需要可以相互调用(interoperable)
4、Promise的初始状态为pending,它可以由此状态转换为fulfilled(本文为了一致把此状态叫做resolved)或者rejected,一旦状态确定,就不可以再次转换为其它状态,状态确定的过程称为settle
三、实现一个promise
1、构造函数
因为标准并没有指定如何构造一个Promise对象,所以我们同样以目前一般Promise实现中通用的方法来构造一个Promise对象,也是ES6原生Promise里所使用的方式,即:
// Promise构造函数接收一个executor函数,executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
var promise = new Promise(function(resolve, reject) {
/*
如果操作成功,调用resolve并传入value
如果操作失败,调用reject并传入reason
*/
})
我们先实现构造函数的框架如下:
function Promise(executor) {
var self = this
self.status = 'pending' // Promise当前的状态
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
executor(resolve, reject) // 执行executor并传入相应的参数
}
上面的代码基本实现了Promise构造函数的主体,但目前还有两个问题:
1、我们给executor函数传了两个参数:resolve和reject,这两个参数目前还没有定义
2、executor有可能会出错(throw),类似下面这样,而如果executor出错,Promise应该被其throw出的值reject:
new Promise(function(resolve, reject) {
throw 2
})
所以我们需要在构造函数里定义resolve和reject这两个函数:
function Promise(executor) {
var self = this
self.status = 'pending' // Promise当前的状态
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
function resolve(value) {
// TODO
}
function reject(reason) {
// TODO
}
try { // 考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,并且在出错后以catch到的值reject掉这个Promise
executor(resolve, reject) // 执行executor
} catch(e) {
reject(e)
}
}
有人可能会问,resolve和reject这两个函数能不能不定义在构造函数里呢?考虑到我们在executor函数里是以resolve(value)
,reject(reason)
的形式调用的这两个函数,而不是以resolve.call(promise, value)
,reject.call(promise, reason)
这种形式调用的,所以这两个函数在调用时的内部也必然有一个隐含的this,也就是说,要么这两个函数是经过bind后传给了executor,要么它们定义在构造函数的内部,使用self来访问所属的Promise对象。所以如果我们想把这两个函数定义在构造函数的外部,确实是可以这么写的:
function resolve() {
// TODO
}
function reject() {
// TODO
}
function Promise(executor) {
try {
executor(resolve.bind(this), reject.bind(this))
} catch(e) {
reject.bind(this)(e)
}
}
但是众所周知,bind也会返回一个新的函数,这么一来还是相当于每个Promise对象都有一对属于自己的resolve和reject函数,就跟写在构造函数内部没什么区别了,所以我们就直接把这两个函数定义在构造函数里面了。不过话说回来,如果浏览器对bind的所优化,使用后一种形式应该可以提升一下内存使用效率。
另外我们这里的实现并没有考虑隐藏this上的变量,这使得这个Promise的状态可以在executor函数外部被改变,在一个靠谱的实现里,构造出的Promise对象的状态和最终结果应当是无法从外部更改的。
接下来,我们实现resolve和reject这两个函数
function Promise(executor) {
// ...
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](v