拥抱JavaScript的异步特性!
以下所有内容都将立即返回,但只有一个地方可以放置发生事件后要运行的代码。
我在这里概述的方法都适用于不同的用例,并根据其复杂性大致排序。
不同之处如下:
等待某些条件变为现实在调用单个回调之前,等待一组方法完成(按任何顺序)在调用回调之前,以特定顺序运行一系列具有共享状态的异步方法
Wait
如果没有任何可访问的回调来告诉您什么时候完成了执行,那么等待某个条件是否为真是非常有用的。
这是一个非常基本的实现,它假设条件在某个时刻变为真。通过一些调整,它可以扩展到更有用(例如,通过设置呼叫限制)。(我昨天才写了这篇!)
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);
});
在这里,我调用了加载ExtJS“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(实现起来很有趣!)。
// Provides a context for running asynchronous methods synchronously
// The context just provides a way of sharing bits of state
// Use 'run' to execute the methods. These should be methods that take a callback and optionally the context as arguments
// Note the callback is provided first, so you have the option of just partially applying your function to the arguments you want
// instead of having to wrap even simple functions in another function
// When adding steps you can supply either just a function or a variable name and a function
// If you supply a variable name then the output of the function (which should be passed into the callback) will be written to the context
createSynchronisedRunner = function (doneFunction) {
var context = {};
var currentPosition = 0;
var steps = [];
// This is the loop. It is triggered again when each method finishes
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;
// Overloads
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();
};
// This is to support more flexible use where you use a done function in a different scope to initialisation
// For example, the done of a test but create in a beforeEach
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的重命名版本。我正在处理的很多东西都将回调作为最后的参数,这样简单的调用就可以这样完成,而不必用一个额外的函数来包装一切。