简体   繁体   English

为什么仅调用同步函数时javascript promises异步?

[英]Why are javascript promises asynchronous when calling only synchronous functions?

In testing I've found that JavaScript Promises are always asynchronous regardless of whether or not they contain any asynchronous functions in their chain. 在测试中,我发现JavaScript Promises 始终是异步的,无论它们的链中是否包含任何异步函数。

Here is some code that shows the order of operations in console. 这是一些代码,显示了控制台中的操作顺序。 If you run it you will see that even though every function is synchronous the output shows both of the aPromise() calls being run in parallel, and "surprisingly this happens after run 2 finishes" not happening before run 2 finishes. 如果运行它,即使每个函数都是同步的,输出aPromise()显示两个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"); }) 

Output to console: 输出到控制台:

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

So, Why are JavaScript promises asynchronous when calling only synchronous functions? 那么, 为什么JavaScript仅在调用同步函数时才承诺异步? What is happening behind the scenes that leads to this behavior? 导致这种行为的幕后发生了什么?


PS In order to better understand this I implemented my own Promise system and I found that making synchronous functions happen in the expected order was easy but making them happen in parallel was something I could only accomplish by putting a setTimeout() of a few milliseconds at every resolve (My guess is that this is not what's happening with vanilla promises and that they are actually being multi threaded). PS为了更好地理解这一点,我实现了自己的Promise系统,发现使同步功能按预期顺序执行很容易,但是使它们并行执行是我只能通过将setTimeout()设置为几毫秒来实现的功能每一个解决方案(我的猜测是香草诺言并没有发生这种情况,它们实际上是多线程的)。

This has been a small problem for one of my programs where I'm traversing a tree applying an array of functions to each node and putting the functions in queue if that node has an asynchronous function already running. 对于我的一个程序来说,这是一个小问题,我遍历一棵树,将一个函数数组应用于每个节点,如果该节点已在运行异步函数,则将该函数放入队列。 Most of the functions are synchronous so the queue is rarely used but upon switching over from callbacks (hell) to Promises I've been having an issue where the queues get used almost always as a result of Promises never running synchronously. 大多数功能都是同步的,因此很少使用队列,但是从回调(地狱)切换到Promises时,我一直遇到一个问题,即由于Promises从未同步运行,因此几乎总是使用队列。 It's not a huge problem but it is a bit of a debugging nightmare. 这不是一个很大的问题,但它有点像调试的噩梦。

1 Year Later EDIT 1年后编辑

I ended up writing some code to deal with this. 我最终写了一些代码来解决这个问题。 It's not amazingly thorough, but I've used it with success to solve the issue I was having. 这不是很彻底,但是我已经成功地使用它来解决我遇到的问题。

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);
    }
}

The callback passed to a Promise constructor is always called synchronously, but the callbacks passed into then are always called asynchronously (you could use setTimeout with a delay of 0 in a userland implementation to achieve that). 传递给无极构造函数的回调总是同步调用,但传递到回调then总是异步调用(你可以使用setTimeout与延迟0在用户空间实现来实现这一点)。

Simplifying your example (and giving the anonymous function's names so I can refer to them) to: 简化您的示例(并提供匿名函数的名称,以便我可以引用它们):

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");
})

Still gives the output in the same order: 仍然以相同的顺序给出输出:

finish run 1
finish run 2
surprisingly this happens after run 2 finishes

Events happen in this order: 事件按以下顺序发生:

  1. The first promise is resolved (synchronously) 第一个承诺得到解决(同步)
  2. callbackA is added to the event loop's queue callbackA被添加到事件循环的队列中
  3. The second promise is resolved 第二个承诺解决了
  4. callbackC is added to the event loop's queue callbackC被添加到事件循环的队列中
  5. There is nothing left to do so the event loop is accessed, callbackA is first in the queue so it is executed, it doesn't return a promise so the intermediate promise for callbackB is immediately resolved synchronously, which appends callbackB to the event loop's queue. 没有什么可做的,因此无法访问事件循环,回调A首先在队列中执行,因此它不会返回承诺,因此回调B的中间承诺会立即被同步解析,这会将回调B附加到事件循环的队列中。
  6. There is nothing left to do so the event loop is accessed, callbackC is first in the queue so it is executed. 没有什么可做的,因此可以访问事件循环,callbackC在队列中位于第一个位置,因此可以执行。
  7. There is nothing left to do so the event loop is accessed, callbackB is first in the queue so it is executed. 没什么可做的,因此可以访问事件循环,回调B在队列中位于第一个位置,因此可以执行。

The easiest way I can think of to work around your problem is to use a library that has an Promise.prototype.isFulfilled function you can use to decide whether to call your second callback synchronously or not. 解决该问题的最简单方法是使用具有Promise.prototype.isFulfilled函数的库,该函数可用于决定是否同步调用第二个回调。 For example: 例如:

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");
})

This outputs: 输出:

finish run 1
surprisingly this happens after run 2 finishes
finish run 2

Your code is fine is you want your promises to run independently and let them execute in their own way, no matter each one complete first. 您的代码很好,因为您希望自己的诺言能够独立运行,并让它们以自己的方式执行,而不管每个先完成。 As soon as your code is async, you cannot predict which one will be completed first (due to the async nature of the event loop ). 一旦代码异步,就无法预测哪个代码将首先完成(由于event loop的异步性质)。

However if you want to catch all your promises after they all completed, you should use Promise.all (which is the equivalent of $.when is jQuery). 但是,如果您希望在所有诺言完成后兑现诺言,则应使用Promise.all (相当于jQuery的$.when Promise.all )。 See: 看到:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM