为什么 javascript 承诺在仅调用同步函数时是异步的?
Why are javascript promises asynchronous when calling only synchronous functions?
在测试中我发现 JavaScript Promise 总是 异步的,不管它们的链中是否包含任何异步函数。
这里有一些代码显示了控制台中的操作顺序。如果你 运行 它你会看到即使每个函数都是同步的输出显示两个 aPromise()
调用是并行的 运行 和 "surprisingly this happens after run 2 finishes"
没有 在 运行 2 完成之前发生。
function aPromise() {
return new Promise(function(resolve, reject) {
console.log("making promise A")
resolve(bPromise());
console.log("promise A resolved")
});
}
function bPromise() {
return new Promise(function(resolve, reject) {
console.log("making and resolving promise B")
resolve();
});
}
aPromise().then(function() {
console.log("finish run 1");
}).then(function() {
console.log("surprisingly this happens after run 2 finishes");
});
aPromise().then(function() {
console.log("finish run 2");
})
输出到控制台:
making promise A
making and resolving promise B
promise A resolved
making promise A
making and resolving promise B
promise A resolved
finish run 1
finish run 2
surprisingly this happens after run 2 finishes
那么,为什么 JavaScript 承诺在仅调用同步函数时是异步的?导致此行为的幕后发生了什么?
P.S。为了更好地理解这一点,我实现了自己的 Promise 系统,我发现让同步函数以预期的顺序发生很容易,但让它们并行发生是我只能通过每次设置几毫秒的 setTimeout() 来完成的解决(我的猜测是,这不是普通承诺所发生的事情,它们实际上是多线程的)。
这对我的一个程序来说是个小问题,我正在遍历一棵树,将函数数组应用于每个节点,如果该节点已经具有异步函数,则将函数放入队列 运行宁。大多数功能都是同步的,因此很少使用队列,但是在从回调(地狱)切换到 Promises 后,我遇到了一个问题,即由于 Promises never 运行ning 同步,队列几乎总是被使用.这不是一个大问题,但有点像调试噩梦。
1 年后编辑
我最后写了一些代码来处理这个问题。它不是非常彻底,但我已经成功地使用它来解决我遇到的问题。
var SyncPromise = function(fn) {
var syncable = this;
syncable.state = "pending";
syncable.value;
var wrappedFn = function(resolve, reject) {
var fakeResolve = function(val) {
syncable.value = val;
syncable.state = "fulfilled";
resolve(val);
}
fn(fakeResolve, reject);
}
var out = new Promise(wrappedFn);
out.syncable = syncable;
return out;
}
SyncPromise.resolved = function(result) {
return new SyncPromise(function(resolve) { resolve(result); });
}
SyncPromise.all = function(promises) {
for(var i = 0; i < promises.length; i++) {
if(promises[i].syncable && promises[i].syncable.state == "fulfilled") {
promises.splice(i, 1);
i--;
}
// else console.log("syncable not fulfilled" + promises[i].syncable.state)
}
if(promises.length == 0)
return SyncPromise.resolved();
else
return new SyncPromise(function(resolve) { Promise.all(promises).then(resolve); });
}
Promise.prototype.syncThen = function (nextFn) {
if(this.syncable && this.syncable.state == "fulfilled") {
//
if(nextFn instanceof Promise) {
return nextFn;
}
else if(typeof nextFn == "function") {
var val = this.syncable.value;
var out = nextFn(val);
return new SyncPromise(function(resolve) { resolve(out); });
}
else {
PINE.err("nextFn is not a function or promise", nextFn);
}
}
else {
// console.log("default promise");
return this.then(nextFn);
}
}
传递给 Promise 构造函数的回调总是被同步调用,但是传递给 then
的回调总是被异步调用(你可以使用 setTimeout
延迟 0
实现该目标的用户空间实现)。
将您的示例简化(并提供匿名函数的名称以便我可以参考):
Promise.resolve().then(function callbackA () {
console.log("finish run 1");
}).then(function callbackB () {
console.log("surprisingly this happens after run 2 finishes");
});
Promise.resolve().then(function callbackC () {
console.log("finish run 2");
})
仍然以相同的顺序给出输出:
finish run 1
finish run 2
surprisingly this happens after run 2 finishes
事件按以下顺序发生:
- 第一个承诺已解决(同步)
- callbackA 被添加到事件循环的队列中
- 第二个承诺已解决
- callbackC 被添加到事件循环的队列中
- 没有什么可做的,所以事件循环被访问,callbackA 在队列中排在第一位,所以它被执行,它没有 return 承诺,所以 callbackB 的中间承诺立即同步解决, 它将 callbackB 附加到事件循环的队列中。
- 没有什么可做的,所以访问事件循环,callbackC 在队列中排在第一位,因此它被执行。
- 没有什么可做的,所以访问事件循环,callbackB 在队列中排在第一位,因此它被执行。
我能想到的解决您的问题的最简单方法是使用具有 Promise.prototype.isFulfilled 函数的库,您可以使用它来决定是否同步调用第二个回调。例如:
var Promise = require( 'bluebird' );
Promise.prototype._SEPH_syncThen = function ( callback ) {
return (
this.isPending()
? this.then( callback )
: Promise.resolve( callback( this.value() ) )
);
}
Promise.resolve()._SEPH_syncThen(function callbackA () {
console.log("finish run 1");
})._SEPH_syncThen(function callbackB () {
console.log("surprisingly this happens after run 2 finishes");
});
Promise.resolve()._SEPH_syncThen(function callbackC () {
console.log("finish run 2");
})
这输出:
finish run 1
surprisingly this happens after run 2 finishes
finish run 2
你的代码很好,你希望你的承诺 运行 独立并让它们以自己的方式执行,无论每个先完成。一旦您的代码是异步的,您就无法预测哪一个将首先完成(由于 event loop
的异步性质)。
但是如果你想在所有的 promise 都完成后捕获它们,你应该使用 Promise.all
(相当于 $.when
是 jQuery)。
参见:
在测试中我发现 JavaScript Promise 总是 异步的,不管它们的链中是否包含任何异步函数。
这里有一些代码显示了控制台中的操作顺序。如果你 运行 它你会看到即使每个函数都是同步的输出显示两个 aPromise()
调用是并行的 运行 和 "surprisingly this happens after run 2 finishes"
没有 在 运行 2 完成之前发生。
function aPromise() {
return new Promise(function(resolve, reject) {
console.log("making promise A")
resolve(bPromise());
console.log("promise A resolved")
});
}
function bPromise() {
return new Promise(function(resolve, reject) {
console.log("making and resolving promise B")
resolve();
});
}
aPromise().then(function() {
console.log("finish run 1");
}).then(function() {
console.log("surprisingly this happens after run 2 finishes");
});
aPromise().then(function() {
console.log("finish run 2");
})
输出到控制台:
making promise A
making and resolving promise B
promise A resolved
making promise A
making and resolving promise B
promise A resolved
finish run 1
finish run 2
surprisingly this happens after run 2 finishes
那么,为什么 JavaScript 承诺在仅调用同步函数时是异步的?导致此行为的幕后发生了什么?
P.S。为了更好地理解这一点,我实现了自己的 Promise 系统,我发现让同步函数以预期的顺序发生很容易,但让它们并行发生是我只能通过每次设置几毫秒的 setTimeout() 来完成的解决(我的猜测是,这不是普通承诺所发生的事情,它们实际上是多线程的)。
这对我的一个程序来说是个小问题,我正在遍历一棵树,将函数数组应用于每个节点,如果该节点已经具有异步函数,则将函数放入队列 运行宁。大多数功能都是同步的,因此很少使用队列,但是在从回调(地狱)切换到 Promises 后,我遇到了一个问题,即由于 Promises never 运行ning 同步,队列几乎总是被使用.这不是一个大问题,但有点像调试噩梦。
1 年后编辑
我最后写了一些代码来处理这个问题。它不是非常彻底,但我已经成功地使用它来解决我遇到的问题。
var SyncPromise = function(fn) {
var syncable = this;
syncable.state = "pending";
syncable.value;
var wrappedFn = function(resolve, reject) {
var fakeResolve = function(val) {
syncable.value = val;
syncable.state = "fulfilled";
resolve(val);
}
fn(fakeResolve, reject);
}
var out = new Promise(wrappedFn);
out.syncable = syncable;
return out;
}
SyncPromise.resolved = function(result) {
return new SyncPromise(function(resolve) { resolve(result); });
}
SyncPromise.all = function(promises) {
for(var i = 0; i < promises.length; i++) {
if(promises[i].syncable && promises[i].syncable.state == "fulfilled") {
promises.splice(i, 1);
i--;
}
// else console.log("syncable not fulfilled" + promises[i].syncable.state)
}
if(promises.length == 0)
return SyncPromise.resolved();
else
return new SyncPromise(function(resolve) { Promise.all(promises).then(resolve); });
}
Promise.prototype.syncThen = function (nextFn) {
if(this.syncable && this.syncable.state == "fulfilled") {
//
if(nextFn instanceof Promise) {
return nextFn;
}
else if(typeof nextFn == "function") {
var val = this.syncable.value;
var out = nextFn(val);
return new SyncPromise(function(resolve) { resolve(out); });
}
else {
PINE.err("nextFn is not a function or promise", nextFn);
}
}
else {
// console.log("default promise");
return this.then(nextFn);
}
}
传递给 Promise 构造函数的回调总是被同步调用,但是传递给 then
的回调总是被异步调用(你可以使用 setTimeout
延迟 0
实现该目标的用户空间实现)。
将您的示例简化(并提供匿名函数的名称以便我可以参考):
Promise.resolve().then(function callbackA () {
console.log("finish run 1");
}).then(function callbackB () {
console.log("surprisingly this happens after run 2 finishes");
});
Promise.resolve().then(function callbackC () {
console.log("finish run 2");
})
仍然以相同的顺序给出输出:
finish run 1
finish run 2
surprisingly this happens after run 2 finishes
事件按以下顺序发生:
- 第一个承诺已解决(同步)
- callbackA 被添加到事件循环的队列中
- 第二个承诺已解决
- callbackC 被添加到事件循环的队列中
- 没有什么可做的,所以事件循环被访问,callbackA 在队列中排在第一位,所以它被执行,它没有 return 承诺,所以 callbackB 的中间承诺立即同步解决, 它将 callbackB 附加到事件循环的队列中。
- 没有什么可做的,所以访问事件循环,callbackC 在队列中排在第一位,因此它被执行。
- 没有什么可做的,所以访问事件循环,callbackB 在队列中排在第一位,因此它被执行。
我能想到的解决您的问题的最简单方法是使用具有 Promise.prototype.isFulfilled 函数的库,您可以使用它来决定是否同步调用第二个回调。例如:
var Promise = require( 'bluebird' );
Promise.prototype._SEPH_syncThen = function ( callback ) {
return (
this.isPending()
? this.then( callback )
: Promise.resolve( callback( this.value() ) )
);
}
Promise.resolve()._SEPH_syncThen(function callbackA () {
console.log("finish run 1");
})._SEPH_syncThen(function callbackB () {
console.log("surprisingly this happens after run 2 finishes");
});
Promise.resolve()._SEPH_syncThen(function callbackC () {
console.log("finish run 2");
})
这输出:
finish run 1
surprisingly this happens after run 2 finishes
finish run 2
你的代码很好,你希望你的承诺 运行 独立并让它们以自己的方式执行,无论每个先完成。一旦您的代码是异步的,您就无法预测哪一个将首先完成(由于 event loop
的异步性质)。
但是如果你想在所有的 promise 都完成后捕获它们,你应该使用 Promise.all
(相当于 $.when
是 jQuery)。
参见: