为什么在只调用同步函数时,javascript承诺是异步的?

时间:2016-04-19 18:43:40

标签: javascript asynchronous promise synchronous

在测试中,我发现JavaScript Promises 总是异步,无论它们是否在其链中包含任何异步函数。

以下是一些显示控制台操作顺序的代码。如果运行它,您将看到即使每个函数都是同步的,输出也会显示两个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()来实现的。解决(我的猜测是,这不是vanilla承诺发生的事情,而且它们实际上是多线程的。)

对于我的一个程序来说,这是一个小问题,我正在遍历一个树,将一个函数数组应用于每个节点,如果该节点已经运行了异步函数,则将这些函数放入队列中。大多数函数是同步的,因此很少使用队列,但是从回调(地狱)切换到Promises时,我一直遇到一个问题,即队列几乎总是被使用,因为Promises从不同步运行。这不是一个大问题,但这是一个调试噩梦。

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

2 个答案:

答案 0 :(得分:14)

传递给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

事件按此顺序发生:

  1. 第一个承诺已经解决(同步)
  2. callbackA被添加到事件循环的队列
  3. 第二个承诺已经解决
  4. callbackC被添加到事件循环的队列
  5. 没有任何事情可以访问事件循环,callbackA首先在队列中执行,它不会返回一个promise,因此callbackB的中间承诺会立即同步解析,这会将callbackB附加到事件循环的队列。
  6. 没有任何事情可以访问事件循环,callbackC首先在队列中执行。
  7. 没有任何事情可以访问事件循环,callbackB首先在队列中执行。
  8. 我能想到解决问题的最简单方法是使用具有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
    

答案 1 :(得分:0)

您的代码很好,您希望您的承诺能够独立运行,并让它们以自己的方式执行,无论每个首先完成。一旦您的代码异步,您就无法预测哪一个将首先完成(由于event loop的异步特性)。

但是,如果你想在完成所有承诺后抓住所有承诺,你应该使用Promise.all(相当于$.when是jQuery)。 见: