Javascript 处理相互依赖和/或分层异步调用
例如,假设我想从某处获取一个文件列表,然后加载这些文件的内容,最后将它们显示给用户。在同步模型中,它类似于以下内容(伪代码): 这为用户提供了良好的反馈,如果我认为有必要,我可以将代码片段移动到函数中。生活很简单 现在,粉碎我的梦想:Javascript 处理相互依赖和/或分层异步调用,javascript,asynchronous,loose-coupling,waterfall,Javascript,Asynchronous,Loose Coupling,Waterfall,例如,假设我想从某处获取一个文件列表,然后加载这些文件的内容,最后将它们显示给用户。在同步模型中,它类似于以下内容(伪代码): 这为用户提供了良好的反馈,如果我认为有必要,我可以将代码片段移动到函数中。生活很简单 现在,粉碎我的梦想:fetchFiles()和loadFile()实际上是异步的。简单的解决方法是将它们转换为同步函数。但是,如果浏览器在等待呼叫完成时锁定,这是不好的 我如何处理多个相互依赖和/或分层异步调用,而不必以经典的reductio-ad-spaghettum方式越来越深入地
fetchFiles()
和loadFile()
实际上是异步的。简单的解决方法是将它们转换为同步函数。但是,如果浏览器在等待呼叫完成时锁定,这是不好的
我如何处理多个相互依赖和/或分层异步调用,而不必以经典的reductio-ad-spaghettum方式越来越深入地钻研无尽的回调链?是否有一个经过验证的范例可以在保持代码松散耦合的同时干净地处理这些问题?听起来像是您需要的。下面是一些未经测试的代码,它们可能会帮助您找到正确的方向:
$.when(fetchFiles(source)).then(function(file_list) {
if (!file_list) {
display('failed to fetch list');
} else {
for (file in file_list) {
$.when(loadFile(file)).then(function(data){
if (!data) {
display('failed to load: ' + file);
} else {
display(data);
}
});
}
}
});
我还发现了另一个例子,它为延迟对象提供了一些使用案例。如果您不想使用jQuery,那么可以使用web Worker和同步请求。除10年前的任何Internet Explorer版本外,所有主要浏览器都支持Web Worker 基本上,如果您不完全确定web worker是什么,可以将其视为浏览器在独立线程上执行专用JavaScript的一种方式,而不会影响主线程(注意:在单核CPU上,两个线程将交替运行。幸运的是,现在大多数计算机都配备了双核CPU)。通常,web工作人员被保留用于复杂的计算或一些密集的处理任务。请记住,web worker中的任何代码都不能引用DOM,也不能引用尚未传递给它的任何全局数据结构。从本质上讲,web工作程序独立于主线程运行。worker执行的任何代码都应该在其自己的JS文件中与JavaScript代码库的其余部分分开。此外,如果web工作人员需要特定数据才能正常工作,则需要在启动他们时将这些数据传递给他们 另一件值得注意的重要事情是,需要用于加载文件的任何JS库都需要直接复制到worker将执行的JavaScript文件中。这意味着这些库应该首先缩小(如果它们还没有缩小),然后复制并粘贴到文件的顶部 无论如何,我决定写一个基本的模板来告诉你如何处理这个问题。看看下面。请随意提问/批评/等 在希望在主线程上继续执行的JS文件上,需要下面的代码来调用worker
function startWorker(dataObj)
{
var message = {},
worker;
try
{
worker = new Worker('workers/getFileData.js');
}
catch(error)
{
// Throw error
}
message.data = dataObj;
// all data is communicated to the worker in JSON format
message = JSON.stringify(message);
// This is the function that will handle all data returned by the worker
worker.onMessage = function(e)
{
display(JSON.parse(e.data));
}
worker.postMessage(message);
}
然后,在为工作人员准备的单独文件中(正如您在上面的代码中看到的,我将我的文件命名为getFileData.js
),编写如下内容
function fetchFiles(source)
{
// Put your code here
// Keep in mind that any requests made should be synchronous as this should not
// impact the main thread
}
function loadFile(file)
{
// Put your code here
// Keep in mind that any requests made should be synchronous as this should not
// impact the main thread
}
onmessage = function(e)
{
var response = [],
data = JSON.parse(e.data),
file_list = fetchFiles(data.source),
file, fileData;
if (!file_list)
{
response.push('failed to fetch list');
}
else
{
for (file in file_list)
{ // iteration, not enumeration
fileData = loadFile(file);
if (!fileData)
{
response.push('failed to load: ' + file);
}
else
{
response.push(fileData);
}
}
}
response = JSON.stringify(response);
postMessage(response);
close();
}
另外,我还找到了另一条线索,它可以更好地帮助您理解将同步请求与web workers结合使用的优缺点
是一个流行的异步流控制库,经常与node.js一起使用。我个人从未在浏览器中使用过它,但显然它在浏览器中也可以使用
本例(理论上)运行两个函数,返回所有文件名及其加载状态的对象async.map
并行运行,而瀑布
是一个系列,将每一步的结果传递给下一步
这里我假设您的两个异步函数接受回调。如果没有,我需要更多关于如何使用它们的信息(它们是否在完成时触发事件?等等)
瀑布式
接受函数数组并按顺序执行它们,将每个函数的结果作为参数传递给下一个函数,并将回调函数作为最后一个参数传递给下一个函数,最后一个参数由错误调用,或者由函数生成的数据调用
当然,您可以在这两者之间或周围添加任意数量的不同异步回调,而无需更改代码的结构<代码>瀑布式实际上只是10种不同的流控制结构中的一种,因此您有很多选择(尽管我几乎总是使用
auto
,它允许您通过类似Makefile的需求语法在同一个函数中混合并行和串行执行)。延迟确实是一种方法。它们准确地捕捉到了您(以及大量异步代码)想要的内容:“走开,做这件可能很昂贵的事情,同时不要打扰我,等您回来后再做。”
您不需要jQuery来使用它们。一个有进取心的人拥有,并且声称你甚至不需要下划线就可以使用它
因此,您的代码可以如下所示:
function fetchFiles(source) {
var dfd = _.Deferred();
// do some kind of thing that takes a long time
doExpensiveThingOne({
source: source,
complete: function(files) {
// this informs the Deferred that it succeeded, and passes
// `files` to all its success ("done") handlers
dfd.resolve(files);
// if you know how to capture an error condition, you can also
// indicate that with dfd.reject(...)
}
});
return dfd;
}
function loadFile(file) {
// same thing!
var dfd = _.Deferred();
doExpensiveThingTwo({
file: file,
complete: function(data) {
dfd.resolve(data);
}
});
return dfd;
}
// and now glue it together
_.when(fetchFiles(source))
.done(function(files) {
for (var file in files) {
_.when(loadFile(file))
.done(function(data) {
display(data);
})
.fail(function() {
display('failed to load: ' + file);
});
}
})
.fail(function() {
display('failed to fetch list');
});
设置有点冗长,但是一旦您编写了处理延迟状态的代码并将其填充到某个函数中,您就不必再担心它了,您就可以很容易地处理实际的事件流。例如:
var file_dfds = [];
for (var file in files) {
file_dfds.push(loadFile(file));
}
_.when(file_dfds)
.done(function(datas) {
// this will only run if and when ALL the files have successfully
// loaded!
});
我在开发一个webapp时遇到了这个问题,下面是我解决这个问题的方法(没有库) 步骤1:编写了一个。没什么特别的。订阅、取消订阅、发布和登录。所有内容(带注释)总共包含93行Javascript。gzip前2.7kb 步骤2:通过让pubsub实现完成繁重的工作,将您试图完成的过程解耦。下面是一个例子:
// listen for when files have been fetched and set up what to do when it comes in
pubsub.notification.subscribe(
"processFetchedResults", // notification to subscribe to
"fetchedFilesProcesser", // subscriber
/* what to do when files have been fetched */
function(params) {
var file_list = params.notificationParams.file_list;
for (file in file_list) { // iteration, not enumeration
var data = loadFile(file);
if (!data) {
display('failed to load: ' + file);
} else {
display(data);
}
}
);
// trigger fetch files
function fetchFiles(source) {
// ajax call to source
// on response code 200 publish "processFetchedResults"
// set publish parameters as ajax call response
pubsub.notification.publish("processFetchedResults", ajaxResponse, "fetchFilesFunction");
}
当然,这是非常冗长的设置和稀缺的魔术幕后。
以下是一些技术细节:
setTimeout
处理触发订阅。这样,他们在一个非阻塞的环境中运行
var file_dfds = [];
for (var file in files) {
file_dfds.push(loadFile(file));
}
_.when(file_dfds)
.done(function(datas) {
// this will only run if and when ALL the files have successfully
// loaded!
});
// listen for when files have been fetched and set up what to do when it comes in
pubsub.notification.subscribe(
"processFetchedResults", // notification to subscribe to
"fetchedFilesProcesser", // subscriber
/* what to do when files have been fetched */
function(params) {
var file_list = params.notificationParams.file_list;
for (file in file_list) { // iteration, not enumeration
var data = loadFile(file);
if (!data) {
display('failed to load: ' + file);
} else {
display(data);
}
}
);
// trigger fetch files
function fetchFiles(source) {
// ajax call to source
// on response code 200 publish "processFetchedResults"
// set publish parameters as ajax call response
pubsub.notification.publish("processFetchedResults", ajaxResponse, "fetchFilesFunction");
}
// async request for files
function fetchFiles(source) {
IO.get(..., function (data, status) {
if(data) {
bean.fire(window, 'fetched_files', data);
} else {
bean.fire(window, 'fetched_files_fail', data, status);
}
});
}
// handler for when we get data
function onFetchedFiles (event, files) {
for (file in files) {
var data = loadFile(file);
if (!data) {
display('failed to load: ' + file);
} else {
display(data);
}
}
}
// handler for failures
function onFetchedFilesFail (event, status) {
display('Failed to fetch list. Reason: ' + status);
}
// subscribe the window to these events
bean.on(window, 'fetched_files', onFetchedFiles);
bean.on(window, 'fetched_files_fail', onFetchedFilesFail);
fetchFiles();