现场演示
鉴于以下功能:
function isGood(number) {
var defer = $q.defer();
$timeout(function() {
if (<some condition on number>) {
defer.resolve();
} else {
defer.reject();
}
}, 100);
return defer.promise;
}
和一组数字(例如 [3, 9, 17, 26, 89]
),我想找到的 第一 “好”的数字。我希望能够这样做:
var arr = [3, 9, 17, 26, 89];
findGoodNumber(arr).then(function(goodNumber) {
console.log('Good number found: ' + goodNumber);
}, function() {
console.log('No good numbers found');
});
这是一个可能的递归版本来实现这个: DEMO
function findGoodNumber(numbers) {
var defer = $q.defer();
if (numbers.length === 0) {
defer.reject();
} else {
var num = numbers.shift();
isGood(num).then(function() {
defer.resolve(num);
}, function() {
findGoodNumber(numbers).then(defer.resolve, defer.reject)
});
}
return defer.promise;
}
我想知道是否有更好的(可能是非递归的)方式?
我想知道是否有更好的方法?
是。避免 延迟反模式!
function isGood(number) {
return $timeout(function() {
if (<some condition on number>) {
return number; // Resolve with the number, simplifies code below
} else {
throw new Error("…");
}
}, 100);
}
function findGoodNumber(numbers) {
if (numbers.length === 0) {
return $q.reject();
} else {
return isGood(numbers.shift()).catch(function() {
return findGoodNumber(numbers);
});
}
}
也许不是递归的?
你可以制定一个链接很多的循环 then
电话,但递归在这里绝对没问题。如果你真的想要循环,它可能看起来像这样:
function findGoodNumber(numbers) {
return numbers.reduce(function(previousFinds, num) {
return previousFinds.catch(function() {
return isGood(num);
});
}, $q.reject());
}
然而,这一点效率较低,因为它总是可以看到 numbers
。 “递归”版本将懒惰地评估它,并且如果当前数字不好则仅进行另一次迭代。
也许更快?
你可以解雇所有人 isGood
并行检查,并等待第一次履行返回。取决于什么 isGood
实际上可行和可并行化的程度,这可能是“更好”。但是,它可能会做很多不必要的工作;您可能希望使用支持取消的promise库。
使用Bluebird库的一个例子,它有一个 any
辅助功能 致力于这项任务:
function findGoodNumber(numbers) {
return Bluebird.any(numbers.map(isGood))
}
这是一种具有不同形式的递归的替代解决方案:
function firstGood(arr){
var i = 0;
return $q.when().then(function consume(){
if(i >= arr.length) return $q.reject(Error("No Number Found"));
return isGood(arr[i++]).catch(consume);
});
}
它与Bergi非常相似,它是关于如果没有像某些库(Bluebird和最近的When)那样实现Promise.reduce而获得的最佳效果。
这是我的版本,只需使用array.map函数
演示
angular.module('MyApp', []).run(function($q, $timeout) {
var arr = [3, 9, 17, 26, 89];
findGoodNumber(arr).then(function(goodNumber) {
console.log('Good number found: ' + goodNumber);
}, function() {
console.log('No good numbers found');
});
function findGoodNumber(numbers) {
var defer = $q.defer();
numbers.forEach(function(num){
isGood(num).then(function(){
defer.resolve(num);
});
});
return defer.promise;
}
function isGood(number) {
var defer = $q.defer();
$timeout(function() {
if (number % 2 === 0) {
defer.resolve();
} else {
defer.reject();
}
}, 1000);
return defer.promise;
}
});
承诺从未打算用作布尔,但这实际上是什么 isGood()
是在做。在这里,我们不仅仅意味着使用布尔值来解析/拒绝承诺。我们的意思是 承诺的状态 传达其状态:
- 待定==尚未知
- 已解决== true
- 拒绝== false
有些人可能认为这是承诺滥用,但试图以这种方式利用承诺是很有趣的。
可以说,有关承诺的主要问题是布尔值:
- 'true'的promise表示将采用成功路径,'false'的promise表示将采用失败路径
- Promise库自然不允许所有必要的布尔代数 - 例如。不,和,或,异或
在更好地探索和记录这一主题之前,需要想象力来克服/利用这些主题 特征。
让我们尝试解决问题(使用jQuery - 我知道它更好)。
首先让我们写一个更明确的版本 isGood()
:
/*
* A function that determines whether a number is an integer or not
* and returns a resolved/rejected promise accordingly.
* In both cases, the promise is resolved/rejected with the original number.
*/
function isGood(number) {
return $.Deferred(function(dfrd) {
if(parseInt(number, 10) == number) {
setTimeout(function() { dfrd.resolve(number); }, 100);//"true"
} else {
setTimeout(function() { dfrd.reject(number); }, 100);//"false"
}
}).promise();
}
我们需要一种“非”方法 - 交换“解决”和“拒绝”的方法。 jQuery承诺没有原生逆变器,所以这里有一个功能来完成这项工作。
/*
* A function that creates and returns a new promise
* whose resolved/rejected state is the inverse of the original promise,
* and which conveys the original promise's value.
*/
function invertPromise(p) {
return $.Deferred(function(dfrd) {
p.then(dfrd.reject, dfrd.resolve);
});
}
现在,问题的一个版本 findGoodNumber()
,但这里利用了重写 isGood()
和 invertPromise()
效用。
/*
* A function that accepts an array of numbers, scans them,
* and returns a resolved promise for the first "good" number,
* or a rejected promise if no "good" numbers are present.
*/
function findGoodNumber(numbers) {
if(numbers.length === 0) {
return $.Deferred.reject().promise();
} else {
return invertPromise(numbers.reduce(function(p, num) {
return p.then(function() {
return invertPromise(isGood(num));
});
}, $.when()));
}
}
最后,相同的调用例程(数据略有不同):
var arr = [3.1, 9.6, 17.0, 26.9, 89];
findGoodNumber(arr).then(function(goodNumber) {
console.log('Good number found: ' + goodNumber);
}, function() {
console.log('No good numbers found');
});
DEMO
将代码转换回Angular / $ q应该非常简单。
说明
该 else
的条款 findGoodNumber()
可能不太明显。它的核心是 numbers.reduce(...)
,建立一个 .then()
链 - 有效地进行同步扫描 numbers
阵列。这是一种熟悉的异步模式。
在没有两个反转的情况下,阵列将被扫描直到第一个 坏号码 找到并导致拒绝将采用失败路径(跳过扫描的其余部分并继续执行失败处理程序)。
但是,我们希望找到第一个 好的号码 采取“失败”的道路 - 因此需要:
- 内部反转:将报告的“true”转换为“false” - 强制跳过其余的扫描
- 外部反转:恢复原始的布尔意义 - “true”最终为“true”,“false”最终为“false”。
你可能需要搞砸了 演示 更好地了解正在发生的事情。
结论
是的,没有递归就可以解决问题。
这个解决方案既不是最简单也不是最有效,但它有望展示promises'状态代表布尔值和实现异步布尔代数的潜力。
替代解决方案
findGoodNumber()
可以通过执行“OR扫描”来编写而无需反转,如下所示:
function findGoodNumber(numbers) {
if(numbers.length === 0) {
return $.Deferred.reject().promise();
} else {
return numbers.reduce(function(p, num) {
return p.then(null, function() {
return isGood(num);
});
}, $.Deferred().reject());
}
}
这是jQuery相当于Bergi的解决方案。
DEMO