
前言
这里不讲仿写,主要讲自己的所得;最近又回顾了一下promise的实现;以及async-await,promise 串行,限流并行;妙哉妙哉。
收获和感受
- 语言的魅力在于组合
- 不定时的回看,看完后有所思,有所得,多实践。才是最关键的。
- 个人认为
promise的关键点在于进度条(状态机)的控制
看完各路大神仿写
最值得改进自己曾经代码的就是它;很多时候我们请求完接口,接着又要请求下一个,可能就嵌套着写了,
ajax(12).then(res =>
ajax(res).then(res1 =>
ajax(res1).then(res2 =>
ajax(res2).then(val => {
console.log(val);
})
)
)
);
远观代码,咦,整齐划一,其实不应该这样写的,曾经的你中招了嘛~~~ 悄悄的去改改吧
ajax(1)
.then(res => ajax(res))
.then(res1 => ajax(res1))
.then(res2 => ajax(res2))
.then(val => {
console.log(val);
})
.catch(err => err); //再来一个兜底
近观慢动作,也是整齐划一,妙哉妙哉
把玩一下串行
上面的代码例子就是一个串行请求,可以写一个通用的函数
//values: () => Promise<any>[]
function promiseReduce(values) {
return values.reduce(
(p, n) =>
(p = p.then(
() => n(),
err => err
)),
Promise.resolve() //默认值加上
);
}
如果我要将每次串行请求的结果收集到数组中,怎么写讷
方法一:简单粗暴
new Promise(res => {
let result = [];
ajax(1)
.then(res => {
result.push(res);
return ajax(2);
})
.then(res1 => {
result.push(res1);
return ajax(3);
})
.then(res2 => {
result.push(res2);
return ajax(4);
})
.then(res3 => {
result.push(res3);
res(result);
})
.catch(err => err);
}).then(data => {
console.log(data);
});
方法二:每次再请求下一个时,添油加醋一番,别有滋味😋
let result = [];
ajax(1)
.then(val => Promise.resolve(result.push(val)))
.then(() => ajax(2))
.then(val => Promise.resolve(result.push(val)))
.then(() => ajax(3))
.then(val => Promise.resolve(result.push(val)))
.then(() => ajax(4))
.then(val => Promise.resolve(result.push(val)))
.then(() => {
console.log(result, 'f');
})
.catch(err => err);
改进上面代码
- 最直观的反应就是如果
list = [() => ajax(), () => ajax(), ...],那就将其改成[() => ajax(), Promise.resolve(result.push(val))), () => ajax(), Promise.resolve(result.push(val))),...] - 不过我们可以改进一下
promiseReduce
function g(val) {
val && result.push(val);
}
function promiseReduce(values, fn) {
return values.reduce((p, n) => {
return (p = p.then(
val => Promise.resolve((fn(val), val)).then(res => n(res)),
err => err
));
}, Promise.resolve());
}
//也可以这样写
function promiseReduce1(values, fn) {
return values.reduce((p, n) => {
return (p = p.then(
val => Promise.resolve((fn(val), val)),
err => err
)).then(() => n());
}, Promise.resolve());
}
对于 reject 的情况,可以每个 promise 兜底,也可以最后兜底。但是结果不一样 调试代码如下
function ajax(val) {
return new Promise((res, rej) => {
setTimeout(() => {
res(val);
}, 1000);
});
}
function g(val) {
val && result.push(val);
}
function promiseReduce(values, fn) {
return values.reduce((p, n) => {
return (p = p.then(
val => Promise.resolve((fn(val), val)).then(res => n(res)),
err => err
));
}, Promise.resolve());
}
function reject(val) {
return new Promise((res, rej) => {
setTimeout(() => {
rej('error');
}, 1000);
});
}
//最后兜底
// function promiseReduce(values, fn) {
// return values.reduce((p, n) => {
// return (p = p.then(val => Promise.resolve((fn(val), val)))).then(() => n());
// }, Promise.resolve());
// }
promiseReduce(
[() => reject(), () => ajax(1), () => ajax(2), () => ajax(3), , () => ajax(4), () => ajax(5)],
g
)
.then(val => {
result.push(val);
console.log(result);
})
.catch(err => {
console.log(err);
});
方法三:其实promise.all,也可以改成串行
//每隔两秒去请求
function ajax(val) {
return new Promise((resovle, reject) => {
setTimeout(() => {
resovle(val);
console.log(val, 'val');
}, val * 1000);
}).catch(err => err);
}
let l = [ajax, ajax, ajax, ajax, ajax];
let config = 2;
let p = Promise.resolve(config);
function fn(p) {
return function (e, i) {
return (p = p.then(res => e(res))); //这边玩法挺多的😄,有待玩耍
};
}
l = l.map(fn(p));
Promise.all(l).then(res => {
console.log(res);
});
利用promise.all可以用来实现限流 ,可以看这篇文章,很有意思
我罗列了几个关键点,欢迎食用😋
- 保障每个都返回
promise,后面都加入回调队列中
build (fn) {
if (this.count < this.limit) {
return this.run(fn)
} else {
return this.enqueue(fn)
}
}
- 没到达限流前先并发执行,一个完成从回调队列中拿来
async run (fn) {
this.count++
// 维护一个计数器
const value = await fn()
this.count--
// 执行完,看看队列有东西没
this.dequeue()
return value
}
-
下面这段代码能做到,
其一:利用达到限流后加入的
promise是新的promise,放入promise.all中,其二:新的
promise它的状态改变后返回的结果,也就是最后promise.all()返回到数组中的结果元素其三: 新的
promise将状态控制权放入回调队列中,等待去执行this.run(fn).then(resolve).catch(reject)
enqueue (fn) {
//新搞一个promise
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject })
})
}
dequeue () {
if (this.count < this.limit && this.queue.length) {
const { fn, resolve, reject } = this.queue.shift()
//await fn() 执行完将结果带入resolve(value)并结束其状态
this.run(fn).then(resolve).catch(reject)
}
}
核心
核心我认为是:promise的进度条怎么控制或者怎么转移,大家都知道是通过resolve或者reject。怎么控制,怎么玩它就是很大的学问了;每个人的想法思路不同,各种组合实现就成了万花筒,妙哉妙哉
简单理解一下
因为一个promise的自身的状态是唯一的,由pending开始到resolved或者rejected(这边用这两个名词代替);
then中的返回值是一个接力棒(并始终返回一个新的promise);又是一个新的开始。进度条控制也就转交给了它。
axios 中间件,async-await 仿写,限流等都有用到
之前的遇到的题
页面上有一个输入框,两个按钮,A 按钮和 B 按钮,点击 A 或者 B 分别会发送一个异步请求,请求完成后,结果会显示在输入框中。
我之前第一想法是 维护一份回调队列,每次点击往队列中加入回调。开关的控制在第一次这个思路类似 vue 的合并更新策略。其实这个思路是错的。理由如下: 主要原因就是:
js去执行a.click(),b.click()和用户点击按钮a,按钮b其实是不一样的,- 前者统一先执行往主线程代码也就是x先
a.click(),其中有微任务的加到微任务队列中,有宏任务的加入宏任务中;再去执行b.click()同理;主线程完事了,再去微任务,然后宏任务; - 后者:先执行完
a.click()的所有任务,然后再去执行b.click()
2.正确解法
const p = Promise.resolve();
function AClick() {
p = p.then(() => {
return new Promise((resolve, reject) => {
ajax().then(() => {
resolve();
console.log('A');
});
});
});
}
function BClick() {
p = p.then(() => {
return new Promise((resolve, reject) => {
ajax().then(() => {
resolve();
console.log('B');
});
});
});
}
如果按钮很多可以写个函数对每个按钮包装一层,类似装饰器
总结
- 多看多理解,代码的魅力亦或是组合
- 看懂了组合,下一步就是掌握其核心的本质