如何用promsie实现观察者模式
这篇文章主要介绍“如何用promsie实现观察者模式”,在日常操作中,相信很多人在如何用promsie实现观察者模式问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何用promsie实现观察者模式”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
创新互联-专业网站定制、快速模板网站建设、高性价比即墨网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式即墨网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖即墨地区。费用合理售后完善,10余年实体公司更值得信赖。
下面我们用一个示例来演示一下什么是观察者模式,有这样一个场景,在一个院子里,有一个小偷,和若干条狗,小偷只要一行动,狗就会叫,这个场景如果用图来展示的话如图:

// 第一版
class Thief {
constructor(){
}
// thief的方法,调用dog的方法;
action(){
dog1.call()
dog2.call()
dog3.call()
}
}
class Dog {
call(){
console.log("狗叫")
}
}
let dog1 = new Dog()
let dog2 = new Dog()
let dog3 = new Dog()
let thief = new Thief();
thief.action()
// 第一版-新增dog4
class Thief {
constructor() {
}
// thief的方法,调用dog的方法;
action() {
dog1.call()
dog2.call()
dog3.call()
// 新增代码
dog4.call()
}
}
class Dog {
call() {
console.log("狗叫")
}
}
let dog1 = new Dog()
let dog2 = new Dog()
let dog3 = new Dog()
// 新增代码:
let dog4 = new Dog()
let thief = new Thief();
thief.action()
// 第二版
// 1、thief增加了list属性,是一个数组
// 2、subscrible方法,追加方法
// 3、publish 发布消息
class Thief {
constructor() {
this.list = []
}
//
subscrible(call) {
this.list.push(call)
}
// publish遍历数组,调用所有方法。
publish() {
for (let i = 0; i < this.list.length; i++) {
this.list[i]()
}
}
// thief的方法内部不会直接调用dog的方法了,
// 而是调用publish
action() {
this.publish()
}
}
class Dog {
call() {
console.log("狗叫")
}
}
let thief = new Thief();
let dog1 = new Dog()
thief.subscrible(dog1.call)
// 每增加一条狗就将狗的call方法追加到list
let dog2 = new Dog()
thief.subscrible(dog2.call)
let dog3 = new Dog()
thief.subscrible(dog3.call)
thief.action()
// 第二版,新增dog4
// 1、thief增加了list属性,是一个数组
// 2、subscrible方法,追加方法
// 3、publish 发布消息
class Thief {
constructor() {
this.list = []
}
//
subscrible(call){
this.list.push(call)
}
// publish遍历数组,调用所有方法。
publish(){
for(let i= 0 ;i
this.list[i]()
}
}
// thief的方法内部不会直接调用dog的方法了,
// 而是调用publish
action() {
this.publish()
}
}
class Dog {
call() {
console.log("狗叫")
}
}
let thief = new Thief();
let dog1 = new Dog()
thief.subscrible(dog1.call)
// 每增加一条狗就将狗的call方法追加到list
let dog2 = new Dog()
thief.subscrible(dog2.call)
let dog3 = new Dog()
thief.subscrible(dog3.call)
// 增加代码:
let dog4 = new Dog()
thief.subscrible(dog4.call)
thief.action()
我们看到,代码中第41行增加dog4,然后调用thief的scrible收集狗的call方法,此时我们调用thief的publish方法,依然能调用所有dog的call方法,但是我们没有修改thief内部的代码,非常优雅的完成了需求,但是如果需求是再增加一个小偷呢?此时代码是什么样的呢?代码如下:
// 第二版,新增thief
class Thief {
constructor() {
this.list = []
}
//
subscrible(call){
this.list.push(call)
}
// publish遍历数组,调用所有方法。
publish(){
for(let i= 0 ;i this.list[i]()
}
}
// thief的方法内部不会直接调用dog的方法了,
// 而是调用publish
action() {
this.publish()
}
}
class Dog {
call() {
console.log("狗叫")
}
}
let thief = new Thief();
// 新增thief代码
let thief1 = new Thief()
let dog1 = new Dog()
thief.subscrible(dog1.call)
// 新增代码
thief1.subscrible(dog1.call)
let dog2 = new Dog()
thief.subscrible(dog2.call)
// 新增代码
thief1.subscrible(dog2.call)
let dog3 = new Dog()
thief.subscrible(dog3.call)
// 新增代码
thief1.subscrible(dog3.call)
thief.action()
// 新增代码
thief1.action()
看看代码,我们在第30行新增了thief1对象,然后分别在第35、39、43行调用thief1的subsctible方法收集dog的call方法。
真是按下葫芦起了瓢,能不能继续优化呢,在使用观察者模式的时候,我们可以将观察者模式抽离出来,抽离成一个pubsub对象,这个对象有拥有两个方法一个属性,代码如下: class Pubsub{ constructor(){ this.list = [] } subscrible(call){ this.list.push(call) } publish(){ for(let i= 0 ;i
仔细阅读源码,我们只是将观察者的一个属性和两个方法抽离出来封装成了一个类,使用这个类时,实例化一下就可以了,然后用这个对象改写上面的代码: let pubsub = new Pubsub();
class Dog {
call() {
console.log("狗叫")
}
}
class Thief {
constructor() {
}
action() {
pubsub.publish()
}
}
let thief = new Thief();
let dog1 = new Dog()
pubsub.subscrible(dog1.call)
let dog2 = new Dog()
pubsub.subscrible(dog2.call)
let dog3 = new Dog()
pubsub.subscrible(dog3.call)
thief.action()
观察代码,小偷在调用action时,不是直接调用狗的call方法,而是通过pubsub,并且收集狗的call方法,也是由pubsub来完成,完全将小偷和狗解耦了。然后我们在添加一个dog4和一个thief1,代码如下: let pubsub = new Pubsub();
class Dog {
call() {
console.log("狗叫")
}
}
class Thief {
constructor() {
}
action() {
pubsub.publish()
}
}
let thief = new Thief();
// 新增thief1代码
let thief1 = new Thief();
let dog1 = new Dog()
pubsub.subscrible(dog1.call)
let dog2 = new Dog()
pubsub.subscrible(dog2.call)
let dog3 = new Dog()
pubsub.subscrible(dog3.call)
// 新增dog4代码
let dog4 = new Dog()
pubsub.subscrible(dog4.call)
thief.action()
仔细阅读源码,第20行和第30行分别添加了thief1和dog4,依然能够实现小偷偷东西,狗会叫的功能,并且不会去修改thief和dog内部的代码,实现了对象之间的解耦。
观察者模式也可以叫做订阅发布模式,本质是一种消息机制,用这种机制我们可以解耦代码中对象互相调用。
第三版代码,我们可以用如下图示来理解: 
观察上图,第三版中图片第一张图多了一个pubsub,我们用一个卫星来代替pubsub,这个版本也比较好维护,添加删除thief或者dog都不会影响到对象。我们在前端应用中使用的redux和vuex都运用了观察者模式,或者叫做订阅者模式,其运行原理也如上图。
文章写到这里,观察者模式基本就聊完了,但是我在观察pubsub这个对象的时候突然想到了promsie,promise天生就是观察者模式,我们可以用promise来改造一下pubsub,代码如下: class Pubsub { constructor() { let promise = new Promise((resolve,reject)=>{ this.resolve = resolve; }) this.promise = promise; } subscrible(call) { this.promise.then(call) } publish() { this.resolve(); }}
Promise天然支持观察者模式,我们将其改造一下,改造成一个Pubsub类,与我们前面实现的Pubsub类效果是一样的。
首先我们在构造函数内部实例化一个promise,并且将这个promsie的resolve的控制权转交到this的resolve属性上。前面写过一篇文章
如何取消promise的调用
,在这篇文章中我们介绍了如何获取promise的控制权。大家有兴趣可以去看一看。
回归正题,我们用promise改写的pubsub来测试下上面的案例,代码如下: class Pubsub {
constructor() {
let promise = new Promise((resolve,reject)=>{
this.resolve = resolve;
})
this.promise = promise;
}
subscrible(call) {
this.promise.then(call)
}
publish() {
this.resolve();
}
}
let pubsub = new Pubsub();
class Dog {
call() {
console.log("狗叫")
}
}
class Thief {
constructor() {
}
action() {
pubsub.publish()
}
}
let thief = new Thief();
// 新增thief1代码
let thief1 = new Thief();
let dog1 = new Dog()
pubsub.subscrible(dog1.call)
let dog2 = new Dog()
pubsub.subscrible(dog2.call)
let dog3 = new Dog()
pubsub.subscrible(dog3.call)
// 新增dog4代码
let dog4 = new Dog()
pubsub.subscrible(dog4.call)
thief.action()
测试代码,我们发现用promise改造的pubsub也能很好的实现观察者模式,这里我们利用了promise的两个知识点,一个是promise的then方法,then方法可以无限追加函数。另外一个是我们得到promise的resolve的控制权,从而控制promise的then链的执行时机。
讲到这里填一下前面文章挖的坑,前面的
如何取消ajax请求的回调
的文章中我们留了一个坑,axios实现取消ajax请求的回调的原理,我们可以回顾下使用axios时如何取消回调,代码如下:
const axios = require('axios')// 1、获取CancelTokenvar CancelToken = axios.CancelToken;// 2、生成sourcevar source = CancelToken.source();console.log(source.token)axios.get('/user/12345', {//get请求在第二个参数 // 3、注入source.token cancelToken: source.token}).catch(function (thrown) { console.log(thrown)});axios.post('/user/12345', {//post请求在第三个参数 name: 'new name'}, { cancelToken: source.token}).catch(e => { console.log(e)});// 4、调用source.cancel("原因"),终止注入了source.token的请求source.cancel('不想请求了');
阅读代码,在第一步和第二步中,我们通过调用axios.CancelToken.source方法得到了一个source对象,第三步中我们在axios调用异步请求时传递cancelToken参数,第四步,在合适的时机调用source.cancle方法取消回调。
我们先看一下CancelToken这个静态方法的代码是如何的: 'use strict';var Cancel = require('./Cancel');/** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; } token.reason = new Cancel(message); resolvePromise(token.reason); });}/** * Throws a `Cancel` if cancellation has been requested. */CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; }};/** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel };};module.exports = CancelToken;
为了直观一些我们将注释和一些基础条件判断去除后,代码如下: function CancelToken(executor) {
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
return;
}
token.reason = message
resolvePromise(token.reason);
});
}
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
阅读源码,我们发现CancelToken是一个类,其构造函数需要传递一个参数,这个参数必须是一个函数,CancelToken通过调用source方法来实例化一个对象。
在CancelToken的构造函数中,实例化一个Promise对象,通过在Promise的外部定义ResolvePromise变量,值实例化promise的时候获取了Promise实例resolve的控制权,然后将控制权封装到cancel函数中,在将cancel函数交给CancelToken构造函数的参数executor函数。
CancelToken在调用cancel方法时,先实例化CancelToken,在实例化过程中,我们将cancel交给了变量cancel,最后将CancelToken的实例token和cancel方法返回出去。
token的实质就是一个promise对象,而cancel方法内部则保存了这个promise的resolve方法。所有我们可以通过cancel来控制promise对象的执行。
接着我们再看一下axios中配置cancelToken参数的核心代码: if (config.cancelToken) { // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // Clean up request request = null; });}
阅读源码,我们发现,当axios发送异步请求配置了acncelToken参数后,axios内部会执行一段代码: config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // Clean up request request = null;});
这段代码会调用传入的axios的cancelToken的promise.then的执行,但是这个promise.then的执行的控制权在cancel函数中,如果我们在这个异步请求的返回前,我们调用了cancle函数就会执行promise.then从而执行request.abort来取消回调。
axios取消异步回调的原理涉及到了两个知识点,首先是利用了xmlhttprequest的abort方法修改readystate的值,其次利用了观察值模式,只不过这个观察者模式用的是promise来实现的。 到此,关于“如何用promsie实现观察者模式”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!
当前题目:如何用promsie实现观察者模式
本文地址:http://ybzwz.com/article/jpdjji.html