下午在写redis存取的时候突然想起来前几个月那个小疑惑,对于 Promise 我现在有了更进一步的理解,继续拿来品一下。
之前写过关于手写Promise
的文章,自己手写一遍Promise,对它的理解会更进一步,手写出来,可以打断点看内部是怎么运行的,不再仅仅停留在能拿来用的层面。
Promise 误区
看下面代码
const p = new Promise((resolve) => setTimeout(() => resolve(), 1000));
p.then(() => {
console.log(666);
});
很简单的一段 Promise 基本操作,请问 666 何时打印?
答:1秒后
这答案既对,又不对
再看下面代码
const p = new Promise((resolve) => setTimeout(() => resolve(), 1000));
const p2 = new Promise((resolve) => {
setTimeout(() => {
console.log(888);
p.then(() => {
console.log(666);
});
}, 1000);
});
p2.then();
动了点小手术,把 p 放在了延时1秒后执行的 p2 中,现在请问666何时打印?
“第一个 setTimeout 1秒后执行,然后在执行一秒后执行的 打印666,所以是 2秒后!”
错!
Why?
让我们先来看一下 简单版Promise constructor实现
class Promise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(executer) {
this.status = Promise.PENDING;
this.value = undefined;
this.successCallback = () => { };
this.failCallback = () => { };
executer(this.resolve.bind(this), this.reject.bind(this));
}
}
很明显,为 Promise 传入的回调函数在constructor中立马被执行了
对于里面什么时候 resolve,resolve之后干什么,请去看我之前写的 Promise 实现
再看 then
then(onFulfilled, onRejected) {
// 没有传函数的时候默认给个什么都不干的函数
if (typeof onFulfilled !== 'function') {
onFulfilled = () => { }
}
if (typeof onRejected !== 'function') {
onRejected = () => { }
}
// 当前状态是pending的时候,说明有任务还没有resolve,这时候暂存一下
if (this.status === Promise.PENDING) {
this.successCallback = () => onFulfilled(this.value)
this.failCallback = () => onRejected(this.value)
}
// 执行到then的时候前面的状态已经改变了,说明走的是同步代码,直接执行就好了
if (this.status === Promise.FULFILLED) {
onFulfilled(this.value)
}
if (this.status === Promise.REJECTED) {
onRejected(this.value)
}
}
then 里的代码都是在准备把 resolve 后的值带出去,走 then 方法传入的两个回调函数
回到问题
const p = new Promise((resolve) => setTimeout(() => resolve(), 1000));
经过以上分析后,可以肯定,这个 Promise 早在实例化的时候就已经开始执行了,执行 then 的时候不过是在把 fulfilled value 取出来。
对并发请求的思考
对于并发请求,通常要用到 Promise.all()
之前没有认真思考过,看到网上文章一般这样写:
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = Promse.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败了,打出 '失败'
})
看他写的 p1,p2,就联系到了我上面所说的问题。
这么写当然没问题,可如果我不一定要执行那个 Promise,又或者我想延时执行它,怎么办?
解决方案
答案就是使用函数包起来!
*又是那一句熟悉的名言 :*
“计算机科学领域的任何问题都可以通过增加一个简介的中间层来解决。”
“Any problem in computer science can be solved by another layer of indirection.”
如果不行,就再加一层!
const p = () =>
new Promise((resolve) => setTimeout(() => resolve(), 1000));
const p2 = () =>
new Promise((resolve) => {
setTimeout(() => {
console.log(888);
p().then(() => {
console.log(666);
});
}, 1000);
});
p2().then();
嗯,就这么简单!
回到我的项目上来,我认为的最好的写法就像下面这样
class simpleGraph{
saveTo(repository: string): Promise<number[]> {
return new Promise((resolve, reject) => {
const tedis = this.getTedis();
const rows = Array.from(this.triples([null, null, null]));
// 删除所有数据后,将每条三元组入库
tedis
.del(repository)
.then(() =>
Promise.all(rows.map((row) => tedis.sadd(repository, row.join(","))))
)
.then(resolve, reject);
});
}
}
这样 Promise.all 执行的时候就达到了真正的并发请求了,脑中想象出万箭齐发的画面了