原则
统一风格
- 统一的代码风格,不能一次代码提交中各种复制粘贴的痕迹。
- 如果你不理解遍历器及状态机的时候,用 Promise (查看例1 getDefer)
- 不需要复杂状态机或者遍历器的时候,不要用 Generator (详情见例2)
- 不需要异步处理多个 Promise 对象的时候,不要用 Async/Await (详情见例2)
例1 getDefer
在 jQuery、 Q.js 等中都有 defer 对象的封装。
实现
这里写了一个精简的 defer 实现:
/**
* getDefer
* @return {Promise.defer} defer对象
*/
exports.getDefer = () => {
const deferred = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
};
使用
// 正常情况下,无须 */async 包裹
function fn() {
const deferred = getDefer();
// 传统方法改造
originFn(...args, (err, result) => {
// 错误结果 reject
if (err) {
deferred.reject(err);
}
// 正确结果 resolve
deferred.resolve(result);
});
// 不关注 callback 方法是否执行完成,返回 deferred 对象
return deferred.promise;
}
示例
改造传统 callback 方法的示例:
function xhrRequest(url) {
const deferred = getDefer();
request.get(url, { timeout: 1e4 }, (err, res) => {
if (err) {
deferred.reject(err);
}
try {
deferred.resolve(JSON.parse(res.body));
} catch (e) {
deferred.reject(err);
}
});
return deferred.promise;
}
替换方案
同时,node.js 原生 Utilities 中已经有了 Promisify 方法来方便 Promise 改造。 参考官方文档: https://nodejs.org/dist/latest-v16.x/docs/api/util.html#util_util_promisify_original
例2 Generator
理解绕弯,调试不便。一般用于底层复杂业务处理及性能优化。
注意:如果不理解迭代器方法及状态机,不要使用 Generator。切记!切记!
原理
解释一下 Generator 函数工作的流程:
function* foo(x) {
var y = 2 * (yield (x + 1));
console.log(x,y)
var z = yield (y / 3);
console.log(x,y,z)
return (x + y + z);
}
// 此时创建, x = 2
var a = foo(2);
// 第一次调用,传值无效
// 执行 yield (x + 1) -> 3
// 停在 L2
console.log(a.next());
// { value: 3, done: false }
// 第二次调用,传入 3
// 替换 var y = 2 * 3
// 执行 yield(y / 3) -> 2
// 停在 L4
console.log(a.next(3));
// 2 6
// { value: 2, done: false }
// 第三次调用,传入 1
// 替换 var z = 1
// return x + y + z 结束
console.log(a.next(1));
// 2 6 1
// { value: 9, done: true }
迭代器示例
将嵌套的数组(Nested Array)打平(成 Plain Array)。
function* iterArr(arr) {
// 迭代器返回一个迭代器对象
if (Array.isArray(arr)) {
// 内节点
for (let i = 0; i < arr.length; i += 1) {
yield* iterArr(arr[i]); // (*)递归
}
} else {
yield arr;
}
}
// 使用 for-of 遍历:
var arr = ['a', ['b', 'c'], ['d', 'e']];
for (const x of iterArr(arr)) {
console.log(x); // a b c d e
}
// 或者直接将迭代器展开:
var arr = ['a', ['b', ['c', ['d', 'e']]]];
const gen = iterArr(arr);
arr = [...gen];
console.log(arr);
// ["a", "b", "c", "d", "e"]
例3 Async/Await
避免无意义的 async 包裹
Async/Await 仅是针对于 Promise 的一种语法糖,有很多时候,被误用了。先举几个常见的例子:
// BAD
async function test() {
// 一些同步操作
return "hello";
}
// GOOD
function test() {
// 一些同步操作
return "hello";
}
// BAD
async function test () {
// 一些同步操作
return await promiseFn();
}
// GOOD
function test () {
// 一些同步操作
return promiseFn();
}
// BAD
async function test () {
// 一些同步操作
const data = await promiseFn();
return { result: data };
}
// GOOD
function test () {
// 一些同步操作
return promiseFn().then((data)=>({ result: data }));
}
Generator 改造为 Async
原始方法:
*createNode({ payload }, { call }) {
const res = yield call(API.createNode, payload);
const { statusCode, result } = res;
const succMessage = '节点创建请求发起成功';
const failMessage = '节点创建请求发起失败';
if (statusCode === 'ok' && result) {
notification.success({ message: result.message || succMessage, top: 64, duration: 3 });
return true;
}
notification.error({ message: result.message || failMessage, top: 64, duration: 3 });
return false;
}
async 改造:
async createNode({ payload }, { call }) {
const res = await call(API.createNode, payload);
const { statusCode, result } = res;
const succMessage = '节点创建请求发起成功';
const failMessage = '节点创建请求发起失败';
if (statusCode === 'ok' && result) {
notification.success({ message: result.message || succMessage, top: 64, duration: 3 });
return true;
}
notification.error({ message: result.message || failMessage, top: 64, duration: 3 });
return false;
}
优化:
// 普通方法
function notify({ statusCode, result } = {}) {
const succMessage = '节点创建请求发起成功';
const failMessage = '节点创建请求发起失败';
if (statusCode === 'ok' && result) {
notification.success({ message: result.message || succMessage, top: 64, duration: 3 });
return true;
}
notification.error({ message: result.message || failMessage, top: 64, duration: 3 });
return false;
}
// 去除包裹
createNode({ payload }, { call }) {
return call(API.createNode, payload).then(notify);
}
RxJS 改造
React 中使用 RxJS
原理
简单的实现:
export const useObservable = (observable) => {
const [value, setValue] = useState()
const [error, setError] = useState()
useEffect(() => {
const subscription = observable.subscribe(setValue, setError)
return () => subscription.unsubscribe()
}, [observable])
return [error, value]
}
更多 useXxx 参考: https://github.com/streamich/react-use
这里推荐两个封装好的库:
- https://github.com/LeetCode-OpenSource/rxjs-hooks
- https://github.com/crimx/observable-hooks
向 RxJS 改造
还是刚才例3 中的代码:
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
// ...
createNode({ payload }, { call }) {
// 其中 API 调用还可以进一步改造
// 如果改造完成,为一个 Observable 或者 Observer,则不再需要 From 转化,直接流式调用即可
return from(call(API.createNode, payload)).pipe(
map(notify)
);
}
调用 Promise 方法
import { from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
from(promiseFn).pipe(
mergeMap((result)=>{
// 处理 promise result
return result;
})
);
Promise 改造为 Observable
创建一个 Observable 对象示例:
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
使用 AsyncSubject 对象示例:
自行了解,较为复杂,需要先了解多播(MultiCasting)。
将一个事件转为 Observable
示例: 在线运行
import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';
// 侦听点击事件
const source = fromEvent(document, 'click');
// 绑定事件处理方法
const example = source.pipe(map(event => `Event time: ${event.timeStamp}`));
// 输出 (示例): 'Event time: 7276.390000000001'
const listener = example.subscribe(val => console.log(val));
// 取消事件绑定
// listener.unsubscribe();
调用 Observable
三个方法均为可选参数:
- next : 每执行一步
- complete: 结束时执行
- error: 出错执行(注意内部报错需要用 throwError 方法)
import { of } from 'rxjs';
const source$ = of([1, 2, 3]);
source$.subscribe({
next: (val) => {
console.log(val);
},
complete: () => {
console.log('done');
},
error: (err) => {
console.error(err);
}
})
参考文档: https://rxjs.dev/guide/observer
经典实例
Debounce 防抖
常用方法,使用场景就不用说了,比如 EventListener 中侦听 scroll 事件等。
示例:在线运行
import { interval, timer } from 'rxjs';
import { debounce } from 'rxjs/operators';
// 每秒投射(Emit)一个值,如 0, 1, 2, 3 ....
const interval$ = interval(1000);
// debounce 时间动态增加 200ms 每次执行
const debouncedInterval = interval$.pipe(debounce(val => timer(val * 200)));
/*
5 秒钟之后, debounce 时间将会超过定时器时间
输出: 0...1...2...3...4......(debounce 时间大于 1s,不再投射值出来)
*/
debouncedInterval.subscribe(val =>
console.log(`Example Two: ${val}`)
);
Throttle 限流
同理,限流(Throttle)也是 RxJS 内置的一个操作符(Operator)。
示例:
import { interval } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
// 每秒投射
const source = interval(1000);
/*
投射一个值, 然后忽略 5s,反复
*/
const example = source.pipe(throttleTime(5000));
// 输出: 0...6...12
example.subscribe(val => console.log(val));
Retry 出错重试
参考: http://rx.js.cool/advanced/delayRetry
undo 撤销操作
参考: http://rx.js.cool/mixin/undo
深入阅读: http://rx.js.cool