使用Angular的$ q.when()解决延迟问题

时间:2022-08-23 11:42:12

I want to use $q.when() to wrap some non-promise callbacks. But, I can't figure out how to resolve the promise from within the callback. What do I do inside the anonymous function to force $q.when() to resolve with my reason?

我想使用$ q.when()来包装一些非承诺的回调。但是,我无法弄清楚如何在回调中解决承诺。我在匿名函数中做什么来强制$ q.when()以我的理由解决?

promises = $q.when(
    notAPromise(
        // this resolves the promise, but does not pass the return value vvv
        function success(res) { return "Special reason"; },
        function failure(res) { return $q.reject('failure'); }
    ) 
);

promises.then(
    // I want success == "Special reason" from ^^^
    function(success){ console.log("Success: " + success); },
    function(failure){ console.log("I can reject easily enough"); }
);

The functionality I want to duplicate is this:

我想复制的功能是这样的:

promises = function(){
    var deferred = $q.defer();

    notAPromise(
        function success(res) { deferred.resolve("Special reason"); },
        function failure(res) { deferred.reject('failure'); }
    ); 

    return deferred.promise;
};

promises.then(
    // success == "Special reason"
    function(success){ console.log("Success: " + success); },
    function(failure){ console.log("I can reject easily enough"); }
);

This is good, but when() looks so nice. I just can't pass the resolve message to then().

这很好,但是当()看起来很好。我只是无法将解析消息传递给then()。


UPDATE

There are better, more robust ways to do this. $q throws exceptions synchronously, and as @Benjamin points out, the major promise libs are moving toward using full Promises in place of Deferreds.

有更好,更强大的方法来做到这一点。 $ q同步抛出异常,正如@Benjamin指出的那样,主要的承诺库正朝着使用完整的Promise代替Deferreds。

That said, this question is looking for a way to do this using $q's when() function. Objectively superior techniques are of course welcome but don't answer this specific question.

也就是说,这个问题正在寻找一种方法来使用$ q的when()函数。客观上优越的技术当然是受欢迎的,但不回答这个具体问题。

2 个解决方案

#1


8  

The core problem

You're basically trying to convert an existing callback API to promises. In Angular $q.when is used for promise aggregation, and for thenable assimilation (that is, working with another promise library). Fear not, as what you want is perfectly doable without the cruft of a manual deferred each time.

您基本上是在尝试将现有的回调API转换为承诺。在Angular $ q.when中用于promise聚合,以及用于可能的同化(即,使用另一个promise库)。不要害怕,因为你想要的东西是完全可行的,如果没有每次延期的手册。

Deferred objects, and the promise constructor

Sadly, with Angular 1.x you're stuck with the outdated deferred interface, that not only like you said is ugly, it's also unsafe (it's risky and throws synchronously).

可悲的是,对于Angular 1.x,你会遇到过时的延迟界面,不仅像你说的那样丑陋,它也是不安全的(它有风险并且同步抛出)。

What you'd like is called the promise constructor, it's what all implementations (Bluebird, Q, When, RSVP, native promises, etc) are switching to since it's nicer and safer.

你想要的是所谓的promise构造函数,它是所有实现(Bluebird,Q,When,RSVP,本地承诺等)正在转换的,因为它更好,更安全。

Here is how your method would look with native promises:

以下是您的方法在本机承诺中的外观:

var promise = new Promise(function(resolve,reject){
    notAPromise(
        function success(res) { resolve("Special reason") },
        function failure(res) { reject(new Error('failure')); } // Always reject
    )                                                          // with errors!
);

You can replicate this functionality in $q of course:

您可以在$ q中复制此功能:

function resolver(handler){
    try {
        var d = $q.defer();
        handler(function(v){ d.resolve(v); }, function(r){ d.reject(r); });
        return d.promise;
    } catch (e) {
        return $q.reject(e); 
        // $exceptionHandler call might be useful here, since it's a throw
    }
}

Which would let you do:

哪个会让你这样做:

var promise = resolver(function(resolve,reject){
    notAPromise(function success(res){ resolve("Special reason"),
                function failure(res){ reject(new Error("failure")); })
});

promise.then(function(){

});

An automatic promisification helper

Of course, it's equally easy to write an automatic promisification method for your specific case. If you work with a lot of APIs with the callback convention fn(onSuccess, onError) you can do:

当然,为您的特定情况编写自动promisification方法同样容易。如果您使用回调约定fn(onSuccess,onError)处理大量API,则可以执行以下操作:

function promisify(fn){
    return function promisified(){
         var args = Array(arguments.length + 2);
         for(var i = 0; i < arguments.length; i++){
             args.push(arguments[i]);
        }
        var d = $q.defer();
        args.push(function(r){ d.resolve(r); });
        args.push(function(r){ d.reject(r); });
        try{
            fn.call(this, args); // call with the arguments
        } catch (e){  // promise returning functions must NEVER sync throw
            return $q.reject(e);
            // $exceptionHandler call might be useful here, since it's a throw
        }
        return d.promise; // return a promise on the API.
    };
}

This would let you do:

这可以让你做到:

var aPromise = promisify(notAPromise);
var promise = aPromise.then(function(val){
    // access res here
    return "special reason";
}).catch(function(e){
    // access rejection value here
    return $q.reject(new Error("failure"));
});

Which is even neater

哪个更整洁

#2


0  

Ok here's my interpretation of what I think you want.

好的,这是我对你的想法的解释。

I am assuming you want to integrate non-promise callbacks with a deferred/promise?

我假设您想要将非承诺回调与延迟/承诺相结合?

The following example uses the wrapCallback function to wrap two non-promise callbacks, successCallback and errCallback. The non-promise callbacks each return a value, and this value will be used to either resolve or reject the deferred.

以下示例使用wrapCallback函数来包装两个非promise回调,successCallback和errCallback。非承诺回调每个都返回一个值,该值将用于解析或拒绝延迟。

I use a random number to determine if the deferred should be resolved or rejected, and it is resolved or rejected with the return value from the non-promise callbacks.

我使用随机数来确定是否应该解析或拒绝延迟,并使用非promise回调的返回值来解析或拒绝它。

Non angular code:

非角度代码:

function printArgs() {
    console.log.apply(console, arguments);
}

var printSuccess = printArgs.bind(null, "success");
var printFail = printArgs.bind(null, "fail");

function successCallback() {
    console.log("success", this);
    return "success-result";
}

function errCallback() {
    console.log("err", this);
    return "err-result";
}

function wrapCallback(dfd, type, callback, ctx) {
    return function () {
        var result = callback.apply(ctx || this, arguments);
        dfd[type](result);
    };
}

Angular code:

角度代码:

var myApp = angular.module('myApp', []);

function MyCtrl($scope, $q) {
    var dfd = $q.defer();
    var wrappedSuccess = wrapCallback(dfd, "resolve", successCallback);
    var wrappedErr = wrapCallback(dfd, "reject", errCallback);
    var rnd = Math.random();
    var success = (rnd > 0.5);
    success ? wrappedSuccess() : wrappedErr();
    console.log(rnd, "calling " + (success ? "success" : "err") + " callback");
    dfd.promise.then(printSuccess, printFail);
}

Example output where the random number is less than 0.5, and so the deferred was rejected.

随机数小于0.5的示例输出,因此拒绝延迟。

err Window /fiddlegrimbo/m2sgu/18/show/
0.11447505658499701 calling err callback
fail err-result

#1


8  

The core problem

You're basically trying to convert an existing callback API to promises. In Angular $q.when is used for promise aggregation, and for thenable assimilation (that is, working with another promise library). Fear not, as what you want is perfectly doable without the cruft of a manual deferred each time.

您基本上是在尝试将现有的回调API转换为承诺。在Angular $ q.when中用于promise聚合,以及用于可能的同化(即,使用另一个promise库)。不要害怕,因为你想要的东西是完全可行的,如果没有每次延期的手册。

Deferred objects, and the promise constructor

Sadly, with Angular 1.x you're stuck with the outdated deferred interface, that not only like you said is ugly, it's also unsafe (it's risky and throws synchronously).

可悲的是,对于Angular 1.x,你会遇到过时的延迟界面,不仅像你说的那样丑陋,它也是不安全的(它有风险并且同步抛出)。

What you'd like is called the promise constructor, it's what all implementations (Bluebird, Q, When, RSVP, native promises, etc) are switching to since it's nicer and safer.

你想要的是所谓的promise构造函数,它是所有实现(Bluebird,Q,When,RSVP,本地承诺等)正在转换的,因为它更好,更安全。

Here is how your method would look with native promises:

以下是您的方法在本机承诺中的外观:

var promise = new Promise(function(resolve,reject){
    notAPromise(
        function success(res) { resolve("Special reason") },
        function failure(res) { reject(new Error('failure')); } // Always reject
    )                                                          // with errors!
);

You can replicate this functionality in $q of course:

您可以在$ q中复制此功能:

function resolver(handler){
    try {
        var d = $q.defer();
        handler(function(v){ d.resolve(v); }, function(r){ d.reject(r); });
        return d.promise;
    } catch (e) {
        return $q.reject(e); 
        // $exceptionHandler call might be useful here, since it's a throw
    }
}

Which would let you do:

哪个会让你这样做:

var promise = resolver(function(resolve,reject){
    notAPromise(function success(res){ resolve("Special reason"),
                function failure(res){ reject(new Error("failure")); })
});

promise.then(function(){

});

An automatic promisification helper

Of course, it's equally easy to write an automatic promisification method for your specific case. If you work with a lot of APIs with the callback convention fn(onSuccess, onError) you can do:

当然,为您的特定情况编写自动promisification方法同样容易。如果您使用回调约定fn(onSuccess,onError)处理大量API,则可以执行以下操作:

function promisify(fn){
    return function promisified(){
         var args = Array(arguments.length + 2);
         for(var i = 0; i < arguments.length; i++){
             args.push(arguments[i]);
        }
        var d = $q.defer();
        args.push(function(r){ d.resolve(r); });
        args.push(function(r){ d.reject(r); });
        try{
            fn.call(this, args); // call with the arguments
        } catch (e){  // promise returning functions must NEVER sync throw
            return $q.reject(e);
            // $exceptionHandler call might be useful here, since it's a throw
        }
        return d.promise; // return a promise on the API.
    };
}

This would let you do:

这可以让你做到:

var aPromise = promisify(notAPromise);
var promise = aPromise.then(function(val){
    // access res here
    return "special reason";
}).catch(function(e){
    // access rejection value here
    return $q.reject(new Error("failure"));
});

Which is even neater

哪个更整洁

#2


0  

Ok here's my interpretation of what I think you want.

好的,这是我对你的想法的解释。

I am assuming you want to integrate non-promise callbacks with a deferred/promise?

我假设您想要将非承诺回调与延迟/承诺相结合?

The following example uses the wrapCallback function to wrap two non-promise callbacks, successCallback and errCallback. The non-promise callbacks each return a value, and this value will be used to either resolve or reject the deferred.

以下示例使用wrapCallback函数来包装两个非promise回调,successCallback和errCallback。非承诺回调每个都返回一个值,该值将用于解析或拒绝延迟。

I use a random number to determine if the deferred should be resolved or rejected, and it is resolved or rejected with the return value from the non-promise callbacks.

我使用随机数来确定是否应该解析或拒绝延迟,并使用非promise回调的返回值来解析或拒绝它。

Non angular code:

非角度代码:

function printArgs() {
    console.log.apply(console, arguments);
}

var printSuccess = printArgs.bind(null, "success");
var printFail = printArgs.bind(null, "fail");

function successCallback() {
    console.log("success", this);
    return "success-result";
}

function errCallback() {
    console.log("err", this);
    return "err-result";
}

function wrapCallback(dfd, type, callback, ctx) {
    return function () {
        var result = callback.apply(ctx || this, arguments);
        dfd[type](result);
    };
}

Angular code:

角度代码:

var myApp = angular.module('myApp', []);

function MyCtrl($scope, $q) {
    var dfd = $q.defer();
    var wrappedSuccess = wrapCallback(dfd, "resolve", successCallback);
    var wrappedErr = wrapCallback(dfd, "reject", errCallback);
    var rnd = Math.random();
    var success = (rnd > 0.5);
    success ? wrappedSuccess() : wrappedErr();
    console.log(rnd, "calling " + (success ? "success" : "err") + " callback");
    dfd.promise.then(printSuccess, printFail);
}

Example output where the random number is less than 0.5, and so the deferred was rejected.

随机数小于0.5的示例输出,因此拒绝延迟。

err Window /fiddlegrimbo/m2sgu/18/show/
0.11447505658499701 calling err callback
fail err-result