用户:
七式草莓查看:0 回复:5 评论:0 创建时间:2022-08-17T13:06:31
以下是代码(这个代码在其他教程中有):
world.onFluidEnter(({ entity }) => {
if (!entity.isPlayer) return;
var wearable = entity.player.addWearable({
bodyPart: Box3BodyPart.HEAD,
mesh: 'mesh/潜水镜.vb',
orientation: new Box3Quaternion(0, 1, 0, 0),
scale: new Box3Vector3(1, 1, 1),
offset: new Box3Vector3(0, 0, 0.5),
});
entity.nextFluidLeave().then(() => wearable.remove())
});
如果在next事件触发前玩家退出地图,会返回一个reject的Promise(内容:Error: entity destroyed),而then中没有处理reject的函数,所以会报错
下面我说的是触发reject的原因,其他的不说
首先,这个事件处理器是ScriptDispatcher的两个方法(on事件对应ScriptDispatcher.prototype.channel,next事件对应ScriptDispatcher.prototype.future)
下面是ScriptDispatcher的源代码:
class ScriptDispatcher {
constructor(oneShot) {
this.handlers = [];
this.promise = [];
this.destroyed = false;
this.oneShot = false;
exports.DISPATCHER_CREATE_COUNT++;
this.oneShot = !!oneShot;
this.channel = (handler) => {
const record = {
finished: false,
inQueue: true,
handler,
};
if (!this.destroyed) {
exports.DISPATCHER_HANDLER_COUNT++;
this.handlers.push(record);
}
return new api_1.Box3EventHandlerToken(() => {
record.finished = true;
}, () => {
if (this.destroyed) {
return;
}
record.finished = false;
if (!record.inQueue) {
record.inQueue = true;
this.handlers.push(record);
}
}, () => !record.finished);
};
this.future = (filter) => {
if (this.destroyed) {
return Promise.reject(new Error('dispatcher destroyed'));
}
return new Promise((resolve, reject) => {
if (this.destroyed) {
reject(new Error('dispatcher destroyed'));
return;
}
exports.DISPATCHER_PROMISE_COUNT++;
this.promise.push({
finished: false,
filter,
resolve,
reject,
});
});
};
}
_commonDestroy() {
exports.DISPATCHER_DESTROY_COUNT++;
this.destroyed = true;
const handlers = this.handlers;
for (let i = 0; i < handlers.length; ++i) {
const h = handlers[i];
h.finished = true;
h.inQueue = false;
}
exports.DISPATCHER_HANDLER_COUNT -= handlers.length;
handlers.length = 0;
}
destroy(reason, scheduler) 喵onDestroy();
const promises = this.promise;
promises.forEach((p) => {
if (!p.finished) {
scheduler.scheduleAt(() => {
p.reject(new Error(reason));
return true;
}, scheduler.currentMS());
}
p.finished = true;
});
exports.DISPATCHER_PROMISE_COUNT -= promises.length;
promises.length = 0;
}
notify(ev, handleException) {
let dispatched = false;
const handlers = this.handlers;
for (let i = 0; i < handlers.length; ++i) {
if (this.destroyed) {
break;
}
const h = handlers[i];
if (h.finished) {
continue;
}
dispatched = true;
try {
h.handler.call(null, ev);
} catch (e) {
handleException(e);
}
}
const promises = this.promise;
for (let i = 0; i < promises.length; ++i) {
if (this.destroyed) {
break;
}
const p = promises[i];
const f = p.filter;
if (f) {
try {
if (!f.call(null, ev)) {
continue;
}
} catch (e) {
handleException(e);
}
}
p.finished = true;
dispatched = true;
try {
p.resolve.call(null, ev);
} catch (e) {
handleException(e);
}
}
let ptr = 0;
for (let i = 0; i < promises.length; ++i) {
const p = promises[i];
if (!p.finished) {
exports.DISPATCHER_PROMISE_COUNT--;
promises[ptr++] = p;
}
}
promises.length = ptr;
ptr = 0;
for (let i = 0; i < handlers.length; ++i) {
const h = handlers[i];
if (h.finished) {
h.inQueue = false;
exports.DISPATCHER_HANDLER_COUNT--;
} else {
handlers[ptr++] = h;
}
}
handlers.length = ptr;
if (this.oneShot) {
this._commonDestroy();
promises.forEach((p) => {
if (!p.finished) {
Promise.resolve().then(() => {
p.reject(new Error('reason'));
}).catch((err) => {
throw err;
});
dispatched = true;
}
p.finished = true;
});
exports.DISPATCHER_PROMISE_COUNT -= promises.length;
promises.length = 0;
}
if (dispatched) {
exports.DISPATCHER_EVENT_COUNT++;
}
return dispatched;
}
}
为了解释这个问题,我们需要研究ScriptDispatcher.prototype.future这个函数
下面是ScriptDispatcher.prototype.future的源代码(为了防止找不到)
this.future = (filter) => {
if (this.destroyed) {
return Promise.reject(new Error('dispatcher destroyed'));
}
return new Promise((resolve, reject) => {
if (this.destroyed) {
reject(new Error('dispatcher destroyed'));
return;
}
exports.DISPATCHER_PROMISE_COUNT++;
this.promise.push({
finished: false,
filter,
resolve,
reject,
});
});
};
由这里可以看出来,这个函数返回了一个Promise(使用Promise构造函数需要执行参数里的resolve或reject才能返回),然后把它添加到了一个数组里
我翻了这个类的其他部分代码,发现notify函数有问题
下面是ScriptDispatcher.prototype.notify的源代码(也是为了防止找不到)
notify(ev, handleException) {
let dispatched = false;
const handlers = this.handlers;
for (let i = 0; i < handlers.length; ++i) {
if (this.destroyed) {
break;
}
const h = handlers[i];
if (h.finished) {
continue;
}
dispatched = true;
try {
h.handler.call(null, ev);
} catch (e) {
handleException(e);
}
}
const promises = this.promise;
for (let i = 0; i < promises.length; ++i) {
if (this.destroyed) {
break;
}
const p = promises[i];
const f = p.filter;
if (f) {
try {
if (!f.call(null, ev)) {
continue;
}
} catch (e) {
handleException(e);
}
}
p.finished = true;
dispatched = true;
try {
p.resolve.call(null, ev);
} catch (e) {
handleException(e);
}
}
let ptr = 0;
for (let i = 0; i < promises.length; ++i) {
const p = promises[i];
if (!p.finished) {
exports.DISPATCHER_PROMISE_COUNT--;
promises[ptr++] = p;
}
}
promises.length = ptr;
ptr = 0;
for (let i = 0; i < handlers.length; ++i) {
const h = handlers[i];
if (h.finished) {
h.inQueue = false;
exports.DISPATCHER_HANDLER_COUNT--;
} else {
handlers[ptr++] = h;
}
}
handlers.length = ptr;
if (this.oneShot) 喵onDestroy();
promises.forEach((p) => {
if (!p.finished) {
Promise.resolve().then(() => {
p.reject(new Error('reason'));
}).catch((err) => {
throw err;
});
dispatched = true;
}
p.finished = true;
});
exports.DISPATCHER_PROMISE_COUNT -= promises.length;
promises.length = 0;
}
if (dispatched) {
exports.DISPATCHER_EVENT_COUNT++;
}
return dispatched;
}
这个函数是的上半部分是用来触发ScriptDispatcher.prototype.channel事件的
所以我们要看下半部分(下半部分太复杂了,我给你们解释一下)
1.查找未触发的next事件的Promise
2.触发next事件中传给它的方法(就是nextXXX(() => ...)中的() => ...),如果返回false则等待下一次触发(如果没有这个方法,默认返回true)
3.执行Promise中的resolve函数,把对应的Event返回
上面的就是触发的逻辑,还有oneShot为false,所以不用看了
不过失败是怎么回事,这是destroy方法的功劳
下面是destroy方法的源代码(里面的this._commonDestroy不用看,那个和这个问题没有关系):
destroy(reason, scheduler) {
this._commonDestroy();
const promises = this.promise;
promises.forEach((p) => {
if (!p.finished) {
scheduler.scheduleAt(() => {
p.reject(new Error(reason));
return true;
}, scheduler.currentMS());
}
p.finished = true;
});
exports.DISPATCHER_PROMISE_COUNT -= promises.length;
promises.length = 0;
}
可见,如果有未触发的Promise事件,就返回reject(返回的值是错误类的实例)
这就是原因,那么如何解决呢
1.实例的代码(就是使用了.then方法的)
在后面加个处理失败的空函数即可(就是.then(..., () => {}))
world.onFluidEnter(({ entity }) => {
if (!entity.isPlayer) return;
var wearable = entity.player.addWearable({
bodyPart: Box3BodyPart.HEAD,
mesh: 'mesh/潜水镜.vb',
orientation: new Box3Quaternion(0, 1, 0, 0),
scale: new Box3Vector3(1, 1, 1),
offset: new Box3Vector3(0, 0, 0.5),
});
entity.nextFluidLeave().then(() => wearable.remove(), () => {})
});
2.使用await的
使用try/catch即可
world.onFluidEnter(async({ entity }) => {
if (!entity.isPlayer) return;
var wearable = entity.player.addWearable({
bodyPart: Box3BodyPart.HEAD,
mesh: 'mesh/潜水镜.vb',
orientation: new Box3Quaternion(0, 1, 0, 0),
scale: new Box3Vector3(1, 1, 1),
offset: new Box3Vector3(0, 0, 0.5),
});
try{
await entity.nextFluidLeave()
wearable.remove()
}catch(err){}
});