链接2个异步调用(promise API)以串行运行

Chaining 2 asynchronous calls (promise API) to run serially

本文关键字:运行 API promise 2个 异步 调用 链接      更新时间:2023-09-26

这与我今天发布的问题类似,但需要将请求串联起来。我有两个异步请求,其中第二个请求需要第一个请求的结果来发送查询。

var Db.get = function(key){
    var deferred = $q.defer();
     //send async req
    var req = ....
    req.success = function(d){
        deferred.resolve(d)
    };
    req.failure = function(d){
        deferred.reject(d)
    }
    return deferred.promise;
}
var someFn = function(id){
    Db.get(id, "abc")
        .then(function (d) {
            console.log("At 1")
            Db.get(d.id, "def")
                .then(function (d) {
                    console.log("At 2")
                    return d
                }, function (e) {
                    //error
                });
        }, function (e) {
            //error
        });
    console.log("At 3")
};

我应该认为它是错误的,因为我希望console.log("At 3")永远不会在成功的场景中打印,因为我在console.log("At 2")之后返回。但是当我运行时,在控制台中我看到这些命令

console.log("At 1")
console.log("At 3")
console.log("At 2")

我认为then会阻塞,直到它从承诺得到响应(由get()返回)。someFn中的所有东西都是串行执行的。这个假设错了吗?将两个使用promise的异步操作串联起来串行运行的最佳方法是什么?

谢谢。

编辑:

我尝试了Ketan建议的在AngularJs中链接Ajax调用。
var someFn = function(id){
            Db.get(id, "abc")
                .then(function (d) {
                    console.log("At 1")
                    return Db.get(d.id, "def")
                }).then(function (d) {
                    console.log("At 2")
                    return d
                }, function (e) {
                    //error
                    return null;
                }).then(function (d) {
                    return d;
        });
        console.log("At 3")
    };

仍然,如果我打一个像

这样的电话
var res = someFn(1)
console.log(res) /// undefined

铬端子在undefined之后显示At 2。我不知道为什么someFn返回的结果没有分配给res

您遇到困难的地方是.then实际上并不阻塞。它可以帮助您将同步代码转换为异步代码,但它不会为您这样做。让我们先考虑一下您正在尝试重写的同步代码。假设Db.get是一个同步函数,它返回一个值,而不是一个承诺:

var someFn = function (id){
    try {
        var d = Db.get(id, "abc");
        console.log("At 1");
        var d = Db.get(d.id, "def");
        console.log("At 2")
        return d;
    } catch (ex) {
        console.log("At 3")
    }
};

在这种情况下,当我调用someFn时,我得到一个值,而不是一个承诺。也就是说,整个函数是同步的。

如果我们暂时快进几年,想象我们可以使用ES6。这样我们就可以将函数重写为:

var someFn = $q.async(function* (id){
    try {
        var d = yield Db.get(id, "abc");
        console.log("At 1");
        var d = yield Db.get(d.id, "def");
        console.log("At 2")
        return d;
    } catch (ex) {
        console.log("At 3")
    }
});

看起来非常相似,但是这次我们让Db.get返回一个promise,并且someFn()也将始终返回一个promise。yield关键字实际上会"暂停"当前函数,直到承诺完成。这使得它看起来就像同步代码,但它实际上是异步的。

回到现在,我们需要弄清楚如何写这个。.then调用的第二个参数是一个错误处理程序,因此与ES6示例完全等价的是:

var someFn = function (id){
    return Db.get(id, "abc")
        .then(function (d) {
            console.log("At 1");
            return Db.get(d.id, "def");
        })
        .then(function (d) {
            console.log("At 2");
            return d;
        })
        .then(null, function (ex) {
            console.log("At 3")
        });
});

注意每个返回都只从当前函数作用域返回。没有办法强迫它跳出someFn

另一个有趣的实验是:

Db.get('id', 'abc')
  .then(function () {
    console.log('B');
  });
console.log('A');

以上将始终记录:

A
B

因为.then不阻塞

我想它会阻塞,直到它得到承诺的响应

。JS中的承诺不是透明的、阻塞的未来,而只是一个链回调的模式。promise在回调执行之前返回,At 3.then返回之后记录,但在回调执行之前。如果在回调中使用了return,那么对于外部的someFn来说,这并不重要。

你更愿意用

var someFn = function(id){
    return Db.get(id, "abc")
      .then(function (d) {
        console.log("At 1")
        return Db.get(d.id, "def");
      })
      .then(function (d) {
        console.log("At 2")
        return d
      }, function (e) {
        //error
      });
}
someFn().then(function(d) {
    console.log("At 3")
});