拥抱JavaScript的异步特性!
以下所有方法都会立即返回,但有一个单独的地方可以放置代码,以便在发生某些事件后运行。
我列出的这些方法都是针对不同用例的,并且大致按照它们的复杂程度排序。
不同的事情如下:
- 等待某些条件变为真
- 等待一组方法完成(任意顺序),然后调用单个回调函数
- 在调用回调函数之前,按特定顺序运行一系列具有共享状态的异步方法
等待
在没有可访问的回调告诉您某些操作已经完成时,等待查看某些条件是否为真非常有用。
这是一个相当基本的实现,假设该条件最终会变为真。通过一些微调,它可以扩展为更加有用的形式(例如,通过设置调用限制)。(我昨天才写了这个!)
function waitFor(predicate, successCallback) {
setTimeout(function () {
var result = predicate();
if (result !== undefined)
successCallback(result);
else
waitFor(predicate, successCallback);
}, 100);
}
呼叫代码:
beforeEach(function (done) {
selectListField('A field');
waitFor(function () {
var availableOptions = stores.scrapeStore(optionStore);
if (availableOptions.length !== 0)
return availableOptions;
}, done);
});
我在这里调用一个加载
Ext JS“store”的东西,在store包含某些内容之前等待,然后才继续执行(
beforeEach是
Jasmine测试框架的一部分)。
等待多个操作完成
我还需要在多个方法完成加载后运行单个回调。您可以像这样实现:
createWaitRunner = function (completionCallback) {
var callback = completionCallback;
var completionRecord = [];
var elements = 0;
function maybeFinish() {
var done = completionRecord.every(function (element) {
return element === true
});
if (done)
callback();
}
return {
getNotifier: function (func) {
func = func || function (){};
var index = elements++;
completionRecord[index] = false;
return function () {
func.applyTo(arguments);
completionRecord[index] = true;
maybeFinish();
}
}
}
};
呼叫代码:
var waiter = createWaitRunner(done);
filterList.bindStore = waiter.getNotifier();
includeGrid.reconfigure = waiter.getNotifier(function (store) {
includeStore = store;
});
excludeGrid.reconfigure = waiter.getNotifier(function (store) {
excludeStore = store;
});
你可以等待通知,或者包装使用传递给函数的值的其他函数。当调用所有方法时,
done
将被运行。
按顺序运行异步方法
当我需要按顺序调用一系列异步方法时(如在测试中),我使用了另一种方法。这与
Async 库中的某些功能类似 - series 大致上也是这样做的,我先去看了一下那个库,看它是否符合我的要求。但我认为我的 API 更适用于测试(而且实现起来很有趣!)。
createSynchronisedRunner = function (doneFunction) {
var context = {};
var currentPosition = 0;
var steps = [];
var runNext = function () {
var step = steps[currentPosition];
step.func.call(null,
function (output) {
step.outputHandler(output);
currentPosition++;
if (currentPosition === steps.length)
return;
runNext();
}, context);
};
var api = {};
api.addStep = function (firstArg, secondArg) {
var assignOutput;
var func;
if (secondArg === undefined) {
assignOutput = function () {
};
func = firstArg;
}
else {
var propertyName = firstArg;
assignOutput = function (output) {
context[propertyName] = output;
};
func = secondArg;
}
steps.push({
func: func,
outputHandler: assignOutput
});
};
api.run = function (completedAllCallback) {
completedAllCallback = completedAllCallback || function(){};
var lastStep = steps[steps.length - 1];
var currentHandler = lastStep.outputHandler;
lastStep.outputHandler = function (output) {
currentHandler(output);
completedAllCallback(context);
doneFunction();
};
runNext();
};
api.setDoneCallback = function (done) {
doneFunction = done;
};
return api;
};
呼叫代码:
beforeAll(function (done) {
var runner = createSynchronisedRunner(done);
runner.addStep('attachmentInformation', testEventService.getAttachmentCalled.partiallyApplyTo('cat eating lots of memory.jpg'));
runner.addStep('attachment', getAttachment.partiallyApplyTo("cat eating lots of memory.jpg"));
runner.addStep('noAttachment', getAttachment.partiallyApplyTo("somethingElse.jpg"));
runner.run(function (context) {
attachment = context.attachment;
noAttachment = context.noAttachment;
});
});
这里的“PartiallyApplyTo”基本上是
Douglas Crockford的Curry实现的重命名版本。我正在处理的很多东西都将回调作为最后一个参数,因此可以像这样简单地调用,而不必用额外的函数包装一切。
sleep()
来计时动画。(我知道有更好的方法...)提问者提供的代码在 Chrome 中对我无效,因为浏览器不会在脚本中进行修改后立即更新 DOM,而是等待代码执行完成后再进行任何 DOM 更新,因此脚本会等待所有延迟的总和,然后一次性应用所有 DOM 更新。 - Ari Fordsham