Цепочка 2 асинхронных вызовов (promise API) для последовательного выполнения

Это похоже на вопрос, который я опубликовал сегодня, но требует запрос последовательно. У меня есть два асинхронных запроса, где второму запросу нужен результат первого для отправки запроса.

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 выполняется последовательно. Это предположение неверно? Каков наилучший способ связать две асинхронные операции, которые используют промисы, для последовательного запуска.

Спасибо.

РЕДАКТИРОВАТЬ:

Я попробовал то, что предложил Кетан цепочка вызовов Ajax в AngularJs .

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

терминал Chrome показывает At 2 после undefined. Я не уверен, почему результат, перенастроенный someFn, не назначен res.


person bsr    schedule 01.05.2013    source источник
comment
См. этот вопрос, на который Павел ответил вчера. stackoverflow.com/questions/16284403/   -  person Ketan    schedule 01.05.2013
comment
спасибо Кетан. Попробую.   -  person bsr    schedule 01.05.2013
comment
Потому что someFn ничего не вернул. Вам нужно использовать someFn().then() и получить доступ к значению из функции, переданной then. Посмотрите это видео, в котором я обсуждаю обещания: plus.google.com/events/cljavmi7kpup1fso43k3fkpk2eg.   -  person Josh David Miller    schedule 01.05.2013


Ответы (2)


Я думал, что тогда заблокирует, пока не получит ответ от обещания

Нет. Промисы в JS — это не прозрачные блокирующие фьючерсы, а просто шаблон для цепочки обратных вызовов. Промис возвращается до выполнения обратного вызова, а 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")
});
person Bergi    schedule 01.05.2013
comment
в том же духе, что если какой-то Fn, скажем, 5-й в цепочке. Итак, обещание где-то в цепочке заставляет все функции, вызываемые в цепочке, работать с обещанием. Аналогично генерации исключения по цепочке. Короче говоря, если someFn вызывается другой функцией (someFn2), должен ли вызывающий someFn2 также использовать .then() для вызова someFn2. - person bsr; 01.05.2013

Место, где у вас возникают трудности, заключается в том, что .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 возвращает обещание, и someFn() также всегда возвращает обещание. Ключевое слово 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 не блокирует.

person ForbesLindesay    schedule 01.05.2013