如何写方法修饰器
首先,修饰器是一个函数(高阶函数),它接收一个函数作为参数,并返回一个函数。
返回的函数所接收的参数同原函数,这样可以保持与原函数一致的使用方式。
function decorateFunc(fn) {
// 闭包内部,可以做一些公共的操作
return (...args) => {
// 做一些前置动作,如处理参数等
// 调用原函数
const result = fn(..args);
// 做一些后置动作,如处理原函数的结果等
return result;
}
}
来个例子
给一个异步操作添加超时装饰。
function timeoutDecorator(asyncFn) {
const _wait = 1000;
return (...args) =>
new Promise((resolve, reject) => {
// 这里需要了解,promise的状态一旦改变就无法再次改变
asyncFn(...args).then(resolve, reject);
setTimeout(() => reject('timeout'), _wait);
});
}
function asyncFn() {
return new Promise((resolve) => {
setTimeout(() => resolve('223'), 1500);
});
}
asyncFn = timeoutDecorator(asyncFn);
asyncFn();
使用Promise.race()。
function timeoutDecorator(asyncFn) {
const _wait = 1000;
const genTimeoutPromise =
() => new Promise((_, reject) => setTimeout(reject, _wait));
return (...args) => Promise.race([
asyncFn(...args),
genTimeoutPromise()
]);
}
上面的超时是固定为1000毫秒的,现在将其改为可通过传参设置。
function timeoutDecorator(wait) {
const _wait = wait;
return fn =>
(...args) =>
new Promise((resolve, reject) => {
fn(...args).then(resolve, reject);
setTimeout(() => reject('timeout'), _wait);
});
}
// 使用
asyncFn = timeoutDecorator(1000)(asyncFn);
asyncFn();
其它常用装饰器方法
1. JSON解析
// 可以防止因为JSON.parse报错没有处理而终止程序
function jsonParseDecorator(fn) {
return (...args) =>
new Promise(resolve => {
fn(...args).then(value => {
try {
resolve([null, JSON.parse(value)]);
} catch (error) {
resolve([error, null]);
}
});
});
}
2. 挂起重复的调用
// 若某次调用还未有反馈结果(成功/失败),那么后续的重复调用将被挂起
// 待这次调用获得反馈结果,那么被挂起的后续调用立刻获得同样的反馈结果
function suspendDecorator(fn) {
let _lock = false;
const _waitingQueue = [];
return (...args) =>
new Promise((resolve, reject) => {
if (_lock) {
_waitingQueue.push([resolve, reject]);
} else {
_lock = true;
const cb = resolveOrReject => value => {
resolveOrReject(value);
const idx = resolveOrReject === resolve ? 0 : 1;
while (_waitingQueue.length) _waitingQueue.shift()[idx](value);
_lock = false;
}
fn(...args).then(cb(resolve), cb(reject));
}
_lock = false;
});
}
3. 调用结果缓存
// 若之前的调用有了成功的反馈,那么后面的调用将直接得到这个成功的反馈结果。
// time - 缓存时间(毫秒数)
// - 0 默认值,表示一直缓存
function cacheDecorator(time = 0) {
const _time = time; // 缓存多久
let _value = undefined; // 上次成功的反馈的结果
let _cachedTime = 0; // 上次成功的反馈的缓存时间
return fn =>
(...args) =>
new Promise((resolve, reject) => {
if (
_cachedTime &&
(_time === 0 || _cachedTime + _time >= Date.now())
) {
// 直接返回缓存结果
resolve(_value);
} else {
fn(...args).then(value => {
resolve(value);
_value = value;
_cachedTime = Date.now();
}, reject);
}
});
}
4. 调用出错,返回默认值
// 当调用过程中发生错误,为了使程序进行下去,提供默认值
function defaultValueDecorator(defaultValue) {
const _defaultValue = defaultValue;
return fn =>
(...args) =>
fn(...args).catch(error => {
// 自定义错误处理
console.error(error);
return _defaultValue;
});
}
5.取消请求(2022-03-23补充)
// cancelRef = { value: false };
function cancelDecorator(cancelRef) {
return asyncFn =>
(...args) =>
asyncFn(...args).then(res => {
if (!cancelRef.value) return res;
throw Error(`has canceled`);
});
}
function createCancelRef() {
return { value: false };
}
let cancelRef = createCancelRef();
function doCancel() {
cancelRef.value = true;
cancelRef = createCancelRef();
}
request = cancelDecorator(cancelRef)(request);
使用compose
现在同时对asyncFn进行多个方式装饰。
asyncFn = defaultValueDecorator('111')(
cacheDecorator()(
jsonParseDecorator(
suspendDecorator(
timeoutDecorator(1000)(
asyncFn
)
)
)
)
);
如上面的这样的修饰方法的方式,很丑陋,也不够灵活(修改的话)。
compose
function componse(...decorators) {
return fn =>
decorators.reduceRight((prev, cur) => cur(prev), fn);
}
// 使用compose来修饰
asyncFn = compose(
defaultValueDecorator('111'),
cacheDecorator(),
jsonParseDecorator,
suspendDecorator,
timeoutDecorator(1000)
)(asyncFn);
装饰器 Decorator
只能用于类和类的方法,不能用于函数,函数存在函数提升问题。
class Demo {
@defaultValueDecorator('111')
@cacheDecorator()
@jsonParseDecorator
@suspendDecorator
@timeoutDecorator(1000)
asyncMethod() {
// doSomething
}
}
// 修饰方法需要改写一下
function timeoutDecorator(timeout) {
const _timeout = timeout;
return (target, name, descriptor) => {
const fn = descriptor.value;
descriptor.value = (...args) =>
new Promise((resolve, reject) => {
fn(...args).then(resolve, reject);
setTimeout(() => reject("timeout"), _timeout);
});
return descriptor;
};
}
Vue2源码中一些装饰方法(非异步)
代码文件: src/shared/util.js
once 函数只执行一次
/**
* Ensure a function is called only once.
*/
export function once (fn: Function): Function {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}
cached 缓存函数的执行结果
/**
* Create a cached version of a pure function.
*/
export function cached<F: Function> (fn: F): F {
const cache = Object.create(null)
return (function cachedFn (str: string) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}: any)
}