我已经将kriskowal's Q library用于项目(web scraper / human-activity模拟器)并熟悉promises,返回它们并解析/拒绝它们,以及库的基本异步控制流方法并且错误抛出/捕获机制已被证明是必不可少的。
我遇到了一些问题。我的promise.then
来电和我的回调都有形成金字塔的不可思议的倾向。有时它是出于范围界定的原因,有时则是为了保证某种事件顺序。 (我想我可以通过重构解决其中的一些问题,但是我想要避免"回调地狱"完全。)
此外,调试非常令人沮丧。我花了很多时间console.log
- 我的方式来找出错误和错误的来源;在我最终找到它们之后,我将开始在那里抛出错误并用promise.finally
将它们移到其他地方,但是首先找到错误的过程是艰巨的。
另外,在我的项目中,订单很重要。我需要按顺序完成所有事情。通常我会发现自己生成函数数组,这些函数返回promises,然后使用Array.prototype.reduce
将它们链接到彼此,我认为我不应该这样做。
以下是我使用此缩减技术的一种方法的示例:
removeItem: function (itemId) {
var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);
return this.getPage('/stock.php')
.then(function (webpage) {
var
pageCount = 5,
promiseFunctions = [],
promiseSequence;
// Create an array of promise-yielding functions that can run sequentially.
_.times(pageCount, function (i) {
var promiseFunction = function () {
var
promise,
path;
if (i === 0) {
promise = Q(webpage);
} else {
path = '/stock.php?p=' + i;
promise = this.getPage(path);
}
return promise.then(function (webpage) {
var
removeMatch = webpage.match(removeRegexp),
removePath;
if (removeMatch !== null) {
removePath = removeitemMatch[0];
return this.getPage(removePath)
.delay(1000)
// Stop calling subsequent promises.
.thenResolve(true);
}
// Don't stop calling subsequent promises.
return false;
}.bind(this));
}.bind(this);
promiseFunctions.push(promiseFunction);
}, this);
// Resolve the promises sequentially but stop early if the item is found.
promiseSequence = promiseFunctions.reduce(function (soFar, promiseFunction, index) {
return soFar.then(function (stop) {
if (stop) {
return true;
} else {
return Q.delay(1000).then(promiseFunction);
}
});
}, Q());
return promiseSequence;
}.bind(this))
.fail(function (onRejected) {
console.log(onRejected);
});
},
我有其他方法基本上做同样的事情,但遭受更糟糕的缩进困境。
我考虑使用coalan's async library重构我的项目。它似乎与Q类似,但我想确切地知道它们的区别。我得到的印象是异步更多"以回调为中心" Q是"以承诺为中心"。
问题:鉴于我的问题和项目要求,使用async over Q会获得和/或失去什么?图书馆如何比较? (特别是在顺序执行一系列任务和调试/错误处理方面?)
答案 0 :(得分:18)
两个图书馆都很好。我发现它们有不同的用途,可以串联使用。
Q为开发人员提供了promise对象,这些对象是未来的值表示。适合时间旅行。
Async为开发人员提供了控制结构和聚合操作的异步版本。
来自linter实现的一次尝试的例子证明了库之间的潜在统一性:
function lint(files, callback) {
// Function which returns a promise.
var getMerged = merger('.jslintrc'),
// Result objects to invoke callback with.
results = [];
async.each(files, function (file, callback) {
fs.exists(file, function (exists) {
// Future representation of the file's contents.
var contentsPromise,
// Future representation of JSLINT options from .jslintrc files.
optionPromise;
if (!exists) {
callback();
return;
}
contentsPromise = q.nfcall(fs.readFile, file, 'utf8');
optionPromise = getMerged(path.dirname(file));
// Parallelize IO operations.
q.all([contentsPromise, optionPromise])
.spread(function (contents, option) {
var success = JSLINT(contents, option),
errors,
fileResults;
if (!success) {
errors = JSLINT.data().errors;
fileResults = errors.reduce(function (soFar, error) {
if (error === null) {
return soFar;
}
return soFar.concat({
file: file,
error: error
});
}, []);
results = results.concat(fileResults);
}
process.nextTick(callback);
})
.catch(function (error) {
process.nextTick(function () {
callback(error);
});
})
.done();
});
}, function (error) {
results = results.sort(function (a, b) {
return a.file.charCodeAt(0) - b.file.charCodeAt(0);
});
callback(error, results);
});
}
我想为每个文件做一些可能阻塞的事情。所以async.each
是显而易见的选择。我可以将相关操作 per-iteration 与q.all
并行化,如果它们适用于2个或更多文件,则重用我的选项值。
这里,Async和Q各自影响程序的控制流程,Q表示将来某个时候解析为文件内容的值。这些图书馆合作得很好。一个人不需要选择一个而不是另一个"。
答案 1 :(得分:2)
使用promise promise和javascript lexical scoping可以简化代码中的回调金字塔。
removeItem: function (itemId) {
var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);
var found = false
var promise = getPage('/sock.php')
_.times(5, (i) => {
promise = promise.then((webpage) => {
if (found) return true
var removeMatch = webpage.match(removeRegexp)
var found = removeMath !== null
var nextPage = found ? removeMatch[0] : '/stock.php?p='+i+1
return Q.delay(1000).then(() => this.getPage(nextPage))
})
})
return promise.fail(console.log.bind(console))
},
不应在新的JavaScript代码中使用恕我直言async
。 Promise更易于组合,并允许更直观的代码。
节点没有使用promises的主要原因是由于性能问题,Bluebird和Q等图书馆已经很好地解决了这个问题。
随着async / await语法变得更加主流,promises将为与同步代码看起来非常相似的代码铺平道路。
答案 2 :(得分:-1)
虽然这仍然不是我的问题(Q vs async)的实际答案,但对于我的问题,我发现了Selenium / {{3}成为一个可行的解决方案。
driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.wait(function() {
return driver.getTitle().then(function(title) {
return title === 'webdriver - Google Search';
});
}, 1000);
WebDriver使用队列按顺序执行promises,这极大地有助于控制缩进。它的承诺也与Q的兼容。
创建一系列承诺不再是问题。一个简单的for循环就可以了。
至于在序列中提前停止,请不要这样做。不使用序列,而是使用异步设计和分支。