萌新按照剖析 Promise 之基础篇学习实现了下promise,但是发现有一个很大的问题,那就是then在promise已改变状态下注册方法,该注册方法不会异步执行,变成了立即执行,请教下各位老哥,代码该如何修改(handle方法),还是说其他方法有问题,亦或是萌新我的理解有误。。。
代码如下:
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
function SimplePromise(fn) {
let state = PENDING;
let value = null;
let deferreds = [];
this.then = function (onFulfilled, onRejected) {
return new SimplePromise (function (resolve, reject) {
// 当promise某一结果状态处理函数完成后,应将bridge promise状态改变,并向其传递结果值
handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
function handle(deferred) {
if (state === PENDING) {
deferreds.push(deferred);
return;
}
let cb = state === FULFILLED ? deferred.onFulfilled : deferred.onRejected;
// 当前promise一结果状态未注册相应处理函数,则将状态传递
if (cb === null) {
cb = state === FULFILLED ? deferred.resolve : deferred.reject;
cb(value);
return;
}
try {
let ret = cb(value);
deferred.resolve(ret);
} catch (e) {
deferred.reject(e);
}
}
function finale() {
setTimeout(function () {
deferreds.forEach(deferred => handle(deferred));
}, 0);
}
function resolve(newValue) {
// 用于处理方法返回的结果是promise
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
let then = newValue.then;
if (typeof then === 'function') {
// bridge promise为了获取方法中返回的promise的结果值,向它注册异步操作后的处理函数
then.call(newValue, resolve, reject);
return;
}
}
state = FULFILLED;
value = newValue;
finale();
}
function reject(reason) {
state = REJECTED;
value = reson;
finale();
}
function doResolve(fn, resolve, reject) {
let done = false;
try {
fn(function (value) {
if (done) return;
done = true;
resolve(value);
}, function (reson) {
if (done) return;
done = true;
reject(reson);
})
} catch (e) {
if (done) return;
done = true;
reject(e);
}
}
doResolve(fn, resolve, reject);
}
比如在这种情况下会出现问题: 按规范不是应该先打印aaa吗?
function getUserId() {
return new SimplePromise(function (resolve) {
resolve(9876);
});
}
getUserId()
.then(function (mobile) {
console.log('do sth with', mobile);
return 'ccc';
})
.then(console.log);
console.log('aaa');
打印结果:
do sth with 9876
ccc
aaa
你看看它实现的Resolve,reject方法
都是同步运行的,所以当然不符合规范了
只要在Promise初始化的时候,添加异步就好了
function doResolve(fn, resolve, reject) {
let done = false;
try {
fn(
function(value) {
if (done) return;
done = true;
// 这里加上
setTimeout(function () {
resolve(value);
})
},
function(reson) {
if (done) return;
done = true;
// 这里加上
setTimeout(function () {
reject(reson);
})
}
);
} catch (e) {
if (done) return;
done = true;
// 这里加上
setTimeout(function () {
reject(e)
});
}
}
测试一下
function getUserId() {
return new SimplePromise(function(resolve) {
console.log(0);
resolve(1);
});
}
getUserId()
.then(function(num) {
console.log(num);
return Promise.resolve(2);
})
.then(function (num) {
console.log(num);
return Promise.resolve(3);
})
.then(function (num) {
console.log(num)
});
console.log("aaa");
// 0
// aaa
// 1
// 2
// 3
嗯嗯 这样好像可以了,但我在想能不能resolve,reject让其同步执行,但是实际注册的onFulfilled,onRejected方法异步执行,比如说在handle方法里加个setTimeout;
function handle(deferred) {
if (state === PENDING) {
deferreds.push(deferred);
return;
}
let cb = state === FULFILLED ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
cb = state === FULFILLED ? deferred.resolve : deferred.reject;
cb(value);
return;
}
// 这里加个setTimeout
setTimeout(function() {
try {
let ret = cb(value);
deferred.resolve(ret);
} catch (e) {
deferred.reject(e);
}
}, 0);
}
运行结果和你的那个例子相同,不知道这样加会不会在其他某种情况下出问题?
大佬,我又想了下按照我之前的思路(promise的状态是同步改变,但是让其注册处理方法异步执行),如果把handle里的方法异步执行用process.nextTick()替换setTimeout()是否会更好?
比如说: promise状态处于pending,对其多次调用then,即对同一promise注册多个方法,一旦该promise状态改变,则执行deferreds数组里之前注册的所有对应方法;finale()中调用deferreds里的每个元素时采用setTimeout(),我若要让其该方法在被调用后立即执行,应使用process.nextTick(); 如果再一次调用setTimeout()来执行,在会将该方法放置到timers phase所在queue的队尾。
但就结果而言,这两种方式都会按照then注册方法时候的顺序执行。那么这两种方式在promise实现的情况下是否会有区别,亦或是否正确?
对这部分问题提取了下:
let funs = [];
funs.push(() => console.log(1));
funs.push(() => console.log(2));
funs.push(() => console.log(3));
setTimeout(() => {
funs.forEach(fun => {
setTimeout(fun, 0);
})
}, 0);
// 或者用process.netxTick()
setTimeout(() => {
funs.forEach(fun => {
process.nextTick(fun);
})
}, 0);
我又仔细想了下,then的注册方法不一定会按顺序执行:
只要能精确的控制timers队列的任务插入时机,就会让后注册的处理方法先执行(包括大佬你的改动也会出现问题)
以下是按照我的想法实现出现问题情况:
function getSimplePromise() {
return new SimplePromise(function(resolve) {
setTimeout(() => resolve(1), 2);
});
}
let pp = getSimplePromise();
pp.then(num => console.log('first', num));
pp.then(num => console.log('second', num));
pp.then(num => console.log('third', num));
setTimeout(() => pp.then(() => console.log('haha')), 2);
console.log("aaa");
// aaa
// haha
// first 1
// second 1
// third 1
大佬你的能避免出现以上这种情况,但我如果修改条件为(如下),同样会出现问题
function getSimplePromise() {
return new SimplePromise(function(resolve) {
setTimeout(() => resolve(1), 2);
});
}
let pp = getSimplePromise();
pp.then(num => console.log('first', num));
pp.then(num => console.log('second', num));
pp.then(num => console.log('third', num));
// setTimeout(() => pp.then(() => console.log('haha')), 2);
setTimeout(function () {
setTimeout(() => pp.then(() => console.log('haha')), 0);
}, 2);
console.log("aaa");
// aaa
// haha
// first 1
// second 1
// third 1
又隔一天,哈哈哈 对于timer queue 中callbacks的push时机应该是在交由poll phase来处理,实际执行则是timer queue中;想明白了这点后,让其单个注册方法异步执行,代码改为: (希望这次改动不会出现问题,哈哈哈哈)
function handle(deferred) {
// ....bala bala 这部分代码和之前的一样
// 异步执行单个注册方法
setTimeout(function() {
try {
let ret = cb(value);
deferred.resolve(ret);
} catch (e) {
deferred.reject(e);
}
}, 0);
}
function finale() {
// 这里就应该在promise状态改变后立刻对这个数组进行处理,将其中的所有注册方法压入(这里用的是setTimeout)timers queue中
deferreds.forEach(deferred => handle(deferred));
}