Promise做为异步编程的一种解决方案,比传统的回调和事件更增强大,也是学习前端所必需要掌握的。做为一个有追求的前端,不只要熟练掌握Promise的用法,并且要对其实现原理有必定的理解(说白了,就是面试装逼必备)。虽然网上有不少Promise的实现代码,几百行的,但我的以为,不对异步编程和Promise有必定的理解,那些代码也就是一个板子而已(面试可不能敲板子)。首先默认读者都对Promise对象比较熟悉了,而后将从前端最经常使用的设计模式:发布-订阅和观察者模式的角度来一步一步的实现Promise。javascript
既然Promise是一种异步解决方案,那么在没有Promise对象以前是怎么作异步处理的呢?有两种方法:回调函数和发布-订阅或观察者设计模式。(关于实现异步编程的更多方式请参考个人文章:JavaScript实现异步编程的5种方式)前端
相信回调函数读者都不陌生,毕竟最先接触的也就是回调函数了,并且用回调函数作异步处理也很简单,以nodejs文件系统模块fs为例,读取一个文件通常都会这么作java
fs.readFile("h.js", (err, data) => {
console.log(data.toString())
});复制代码
其缺点也很明显,当异步流程变得复杂,那么回调也会变得很复杂,有时也叫作”回调地狱”,就以文件复制为例node
fs.exists("h.js", exists => { // 文件是否存在
if (exists) {
fs.readFile("h.js", (err, data) => { // 读文件
fs.mkdir(__dirname + "/js/", err => { // 建立目录
fs.writeFile(__dirname + "/js/h.js", data, err => { // 写文件
console.log("复制成功,再回调下去,代码真的很难看得懂")
})
});
});
}
});复制代码
其实代码仍是能阅读的,感谢JS设计者没有把函数的花括号给去掉。像没有花括号的python写回调就是(就是个笑话。不是说python很差,毕竟JavaScript是世界上最好的语言)python
# 这代码属实无法看啊
def callback_1():
# processing ...
def callback_2():
# processing.....
def callback_3():
# processing ....
def callback_4():
#processing .....
def callback_5():
# processing ......
async_function1(callback_5)
async_function2(callback_4)
async_function3(callback_3)
async_function4(callback_2)
async_function5(callback_1)复制代码
第一次学设计模式仍是在学Java和C++的时候,毕竟设计模式就是基于面向对象,让对象解耦而提出的。发布订阅设计模式和观察者模式很像,可是有点细微的区别(面试考点来了)面试
观察者模式 在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。
发布-订阅模式是一种消息传递模式,消息的 发布者 (Publishers)通常将消息发布到特定消息中心, 订阅者( Subscriber) 能够按照本身的需求从消息中心订阅信息,跟消息队列挺相似的。
在观察者模式只有两种组件:接收者和发布者,而发布-订阅模式中则有三种组件:发布者、消息中心和接收者。编程
在代码实现上的差别也比较明显设计模式
观察者设计模式promise
// 观察者设计模式
class Observer {
constructor () {
this.observerList = [];
}
subscribe (observer) {
this.observerList.push(observer)
}
notifyAll (value) {
this.observerList.forEach(observe => observe(value))
}
}复制代码
发布-订阅设计模式(nodejs EventEmitter)闭包
// 发布订阅
class EventEmitter {
constructor () {
this.eventChannel = {}; // 消息中心
}
// subscribe
on (event, callback) {
this.eventChannel[event] ? this.eventChannel[event].push(callback) : this.eventChannel[event] = [callback]
}
// publish
emit (event, ...args) {
this.eventChannel[event] && this.eventChannel[event].forEach(callback => callback(...args))
}
// remove event
remove (event) {
if (this.eventChannel[event]) {
delete this.eventChannel[event]
}
}
// once event
once (event, callback) {
this.on(event, (...args) => {
callback(...args);
this.remove(event)
})
}
}复制代码
从代码中也能看出他们的区别,观察者模式不对事件进行分类,当有事件时,将通知全部观察者。发布-订阅设计模式对事件进行了分类,触发不一样的事件,将通知不一样的观察者。因此能够认为后者就是前者的一个升级版,对通知事件作了更细粒度的划分。
发布-订阅和观察者在异步中的应用
// 观察者
const observer = new Observer();
observer.subscribe(value => {
console.log("第一个观察者,接收到的值为:");
console.log(value)
});
observer.subscribe(value => {
console.log("第二个观察者,接收到的值为");
console.log(value)
});
fs.readFile("h.js", (err, data) => {
observer.notifyAll(data.toString())
});复制代码
// 发布-订阅
const event = new EventEmitter();
event.on("err", console.log);
event.on("data", data => {
// do something
console.log(data)
});
fs.readFile("h.js", (err, data) => {
if (err) event.emit("err", err);
event.emit("data", data.toString())
});复制代码
两种设计模式在异步编程中,都是经过注册全局观察者或全局事件,而后在异步环境里通知全部观察者或触发特定事件来实现异步编程。
劣势也很明显,好比全局观察者/事件过多难以维护,事件名命冲突等等,所以Promise便诞生了。
Promise在必定程度上继承了观察者和发布-订阅设计模式的思想,咱们先从一段Promise代码开始,来分析Promise是如何使用观察者设计模式
const asyncReadFile = filename => new Promise((resolve) => {
fs.readFile(filename, (err, data) => {
resolve(data.toString()); // 发布者 至关于观察者模式的notifyAll(value) 或者发布订阅模式的emit
});
});
asyncReadFile("h.js").then(value => { // 订阅者 至关于观察者模式的subscribe(value => console.log(value)) 或者发布订阅模式的on
console.log(value);
});复制代码
从上面的Promise代码中,我以为Promise方案优于前面的发布-订阅/观察者方案的缘由就是:对异步任务的封装,事件发布者在回调函数里(resolve),事件接收者在对象方法里(then()),使用局部事件,对二者进行了更好的封装,而不是扔在全局中。
基于上面的思想,咱们能够实现一个简单的Promise:MyPromise
class MyPromise {
constructor (run) { // run 函数 (resolve) => any
this.observerList = [];
const notifyAll = value => this.observerList.forEach(callback => callback(value));
run(notifyAll); // !!! 核心
}
subscribe (callback) {
this.observerList.push(callback);
}
}
//
const p = new MyPromise(notifyAll => {
fs.readFile("h.js", (err, data) => {
notifyAll(data.toString()) // resolve
})
});
p.subscribe(data => console.log(data)); // then
复制代码
几行代码就实现了一个简单的Promise,而上面的代码也就是把观察者设计模式稍微改了改而已。
固然还没结束,上面的MyPromise是有问题的。以前说了Promise是对异步任务的封装,能够当作最小异步单元(像回调同样),而异步结果也应该只有一个,即Promise中的resolve只能使用一次,至关于EventEmitter的once事件。而上面实现的MyPromise的notifyAll是能够用屡次的(没有为何),所以这就能够产生异步任务的结果能够不止一个的错误。所以解决方法就是加一个bool变量或者添加状态即pending态和fulfilled态(本质上和一个bool变量是同样的),当notifyAll调用一次后立马锁住notifyAll或者当pending态变为fulfilled态后再次调用notifyAll函数将不起做用。
为了和Promise对象一致,这里使用添加状态的方式(顺便把方法名给改了一下, notifyAll => resolve, subscribe => then)。
const pending = "pending";
const fulfilled = "fulfilled";
class MyPromise {
constructor (run) { // run 函数 (resolve) => any
this.observerList = [];
this.status = pending;
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.observerList.forEach(callback => callback(value));
}
};
run(resolve); // !!! 核心
}
then (callback) {
this.observerList.push(callback);
}
}
const p = new MyPromise(resolve => {
setTimeout(() => {
resolve("hello world");
resolve("hello world2"); // 很差使了
}, 1000);
});
p.then(value => console.log(value));复制代码
貌似开始有点轮廓了,不过如今的MyPromise中的then可没有链式调用,接下来咱们来实现then链,须要注意的在Promise中then方法返回的是一个新的Promise实例不是以前的Promise。因为then方法一直返回新的MyPromise对象,因此须要一个属性来保存惟一的异步结果。另外一方面,在实现then方法依然是注册回调,但实现时须要考虑当前的状态,若是是pending态,咱们须要在返回新的MyPromise的同时,将回调注册到队列中,若是是fulfilled态,那直接返回新的MyPromise对象,并将上一个MyPromise对象的结果给新的MyPromise对象。
const pending = "pending";
const fulfilled = "fulfilled";
class MyPromise {
constructor (run) { // run 函数 (resolve) => any
this.resolvedCallback = [];
this.status = pending;
this.data = void 666; // 保存异步结果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value; // 存一下结果
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
run(resolve); // !!! 核心
}
then (onResolved) {
// 这里须要对onResolved作一下处理,当onResolved不是函数时将它变成函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
switch (this.status) {
case pending: {
return new MyPromise(resolve => {
this.resolvedCallback.push(value => { // 再包装
const result = onResolved(value); // 须要判断一下then接的回调返回的是否是一个MyPromise对象
if (result instanceof MyPromise) {
result.then(resolve) // 若是是,直接使用result.then后的结果,毕竟Promise里面就须要这么作
} else {
resolve(result); // 感觉一下闭包的伟大
}
})
})
}
case fulfilled: {
return new MyPromise(resolve => {
const result = onResolved(this.data); // fulfilled态,this.data必定存在,其实这里就像map过程
if (result instanceof MyPromise) {
result.then(resolve)
} else {
resolve(result); // 闭包真伟大
}
})
}
}
}
}
const p = new MyPromise(resolve => {
setTimeout(() => {
resolve("hello world");
resolve("hello world2"); // 很差使了
}, 1000);
});
p.then(value => value + "dpf")
.then(value => value.toUpperCase())
.then(console.log);复制代码
以上代码须要重点理解,毕竟理解了上面的代码,下面的就很容易了
只有resolve和then的MyPromise对象已经完成。没有测试的库就是耍流氓,没有差错处理的代码也是耍流氓,因此错误处理仍是很重要的。因为一个异步任务可能完不成或者中间会出错,这种状况必须得处理。所以咱们须要加一个状态rejected来表示异步任务出错,而且使用rejectedCallback队列来存储reject发送的错误事件。(前方高能预警,面向try/catch编程开始了)
const pending = "pending";
const fulfilled = "fulfilled";
const rejected = "rejected"; // 添加状态 rejected
class MyPromise {
constructor (run) { // run 函数 (resolve, reject) => any
this.resolvedCallback = [];
this.rejectedCallback = []; // 添加一个处理错误的队列
this.status = pending;
this.data = void 666; // 保存异步结果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value;
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
const reject = err => {
if (this.status === pending) {
this.status = rejected;
this.data = err;
this.rejectedCallback.forEach(callback => callback(this.data));
}
};
try { // 对构造器里传入的函数进行try / catch
run(resolve, reject); // !!! 核心
} catch (e) {
reject(e)
}
}
then (onResolved, onRejected) { // 添加两个监听函数
// 这里须要对onResolved作一下处理,当onResolved不是函数时将它变成函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
switch (this.status) {
case pending: {
return new MyPromise((resolve, reject) => {
this.resolvedCallback.push(value => {
try { // 对整个onResolved进行try / catch
const result = onResolved(value);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e) // 捕获异常,将异常发布
}
});
this.rejectedCallback.push(err => {
try { // 对整个onRejected进行try / catch
const result = onRejected(err);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(err)
}
} catch (e) {
reject(err) // 捕获异常,将异常发布
}
})
})
}
case fulfilled: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e) // 捕获异常,将异常发布
}
})
}
case rejected: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onRejected(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (e) {
reject(e) // 捕获异常,将异常发布
}
})
}
}
}
}
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(new Error("error"));
resolve("hello world"); // 很差使了
resolve("hello world2"); // 很差使了
}, 1000);
});
p.then(value => value + "dpf")
.then(console.log)
.then(() => {}, err => console.log(err));复制代码
能够看出then方法的实现比较复杂,但这是一个核心的方法,实现了这个后面的其余方法就很好实现了,下面给出MyPromise的每个方法的实现。
这个实现很是简单
catch (onRejected) {
return this.then(void 666, onRejected)
}复制代码
static resolve(p) {
if (p instanceof MyPromise) {
return p.then()
}
return new MyPromise((resolve, reject) => {
resolve(p)
})
}复制代码
static reject(p) {
if (p instanceof MyPromise) {
return p.catch()
}
return new MyPromise((resolve, reject) => {
reject(p)
})
}
复制代码
static all (promises) {
return new MyPromise((resolve, reject) => {
try {
let count = 0,
len = promises.length,
value = [];
for (let promise of promises) {
MyPromise.resolve(promise).then(v => {
count ++;
value.push(v);
if (count === len) {
resolve(value)
}
})
}
} catch (e) {
reject(e)
}
});
}复制代码
static race(promises) {
return new MyPromise((resolve, reject) => {
try {
for (let promise of promises) {
MyPromise.resolve(promise).then(resolve)
}
} catch (e) {
reject(e)
}
})
}复制代码
const pending = "pending";
const fulfilled = "fulfilled";
const rejected = "rejected"; // 添加状态 rejected
class MyPromise {
constructor (run) { // run 函数 (resolve, reject) => any
this.resolvedCallback = [];
this.rejectedCallback = []; // 添加一个处理错误的队列
this.status = pending;
this.data = void 666; // 保存异步结果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value;
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
const reject = err => {
if (this.status === pending) {
this.status = rejected;
this.data = err;
this.rejectedCallback.forEach(callback => callback(this.data));
}
};
try { // 对构造器里传入的函数进行try / catch
run(resolve, reject); // !!! 核心
} catch (e) {
reject(e)
}
}
static resolve (p) {
if (p instanceof MyPromise) {
return p.then()
}
return new MyPromise((resolve, reject) => {
resolve(p)
})
}
static reject (p) {
if (p instanceof MyPromise) {
return p.catch()
}
return new MyPromise((resolve, reject) => {
reject(p)
})
}
static all (promises) {
return new MyPromise((resolve, reject) => {
try {
let count = 0,
len = promises.length,
value = [];
for (let promise of promises) {
MyPromise.resolve(promise).then(v => {
count ++;
value.push(v);
if (count === len) {
resolve(value)
}
})
}
} catch (e) {
reject(e)
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
try {
for (let promise of promises) {
MyPromise.resolve(promise).then(resolve)
}
} catch (e) {
reject(e)
}
})
}
catch (onRejected) {
return this.then(void 666, onRejected)
}
then (onResolved, onRejected) { // 添加两个监听函数
// 这里须要对onResolved作一下处理,当onResolved不是函数时将它变成函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
switch (this.status) {
case pending: {
return new MyPromise((resolve, reject) => {
this.resolvedCallback.push(value => {
try { // 对整个onResolved进行try / catch
const result = onResolved(value);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e)
}
});
this.rejectedCallback.push(err => {
try { // 对整个onRejected进行try / catch
const result = onRejected(err);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(err)
}
} catch (e) {
reject(err)
}
})
})
}
case fulfilled: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result); // emit
}
} catch (e) {
reject(e)
}
})
}
case rejected: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onRejected(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (e) {
reject(e)
}
})
}
}
}
}复制代码
本文想要从发布-订阅和观察者模式分析Promise的实现,先从异步编程的演变提及,回调函数到发布-订阅和观察者设计模式,而后发现Promise和观察者设计模式比较相似,因此先从这个角度分析了Promise的实现,固然Promise的功能远不如此,因此本文分析了Promise的经常使用方法的实现原理。Promise的出现改变了传统的异步编程方式,使JavaScript在进行异步编程时更加灵活,代码更加可维护、可阅读。因此做为一个有追求的前端,必需要对Promise的实现有必定的理解。