Machen Sie sich die asynchrone Natur von JavaScript zu eigen!
Alle folgenden geben sofort zurück, haben aber einen einzigen Platz für den Code, den Sie ausführen möchten, nachdem etwas passiert ist.
Die Methoden, die ich hier skizziert habe, sind alle für unterschiedliche Anwendungsfälle gedacht und sind grob nach ihrer Komplexität geordnet.
Die verschiedenen Dinge sind wie folgt:
- Warten, dass eine Bedingung erfüllt wird
- Warten auf den Abschluss einer Reihe von Methoden (in beliebiger Reihenfolge), bevor ein einzelner Rückruf aufgerufen wird
- Ausführung einer Reihe von asynchronen Methoden mit gemeinsamem Status in einer bestimmten Reihenfolge vor dem Aufruf eines Rückrufs
Warten
Warten, um zu sehen, ob eine Bedingung wahr ist, ist nützlich, wenn es keinen zugänglichen Rückruf gibt, um Ihnen mitzuteilen, wann etwas die Ausführung beendet hat.
Dies ist eine ziemlich einfache Implementierung, die davon ausgeht, dass die Bedingung zu einem bestimmten Zeitpunkt wahr wird. Mit ein paar Anpassungen könnte sie noch nützlicher werden (z. B. durch die Festlegung eines Anruflimits). (Ich habe dies erst gestern geschrieben!)
function waitFor(predicate, successCallback) {
setTimeout(function () {
var result = predicate();
if (result !== undefined)
successCallback(result);
else
waitFor(predicate, successCallback);
}, 100);
}
Aufrufender Code:
beforeEach(function (done) {
selectListField('A field');
waitFor(function () {
var availableOptions = stores.scrapeStore(optionStore);
if (availableOptions.length !== 0)
return availableOptions;
}, done);
});
Hier rufe ich etwas auf, das eine Ext JS speichern' und warten, bis der Speicher etwas enthält, bevor sie fortfahren (die beforeEach ist eine Jasmin Test-Framework).
Warten, bis mehrere Dinge abgeschlossen sind
Ich musste auch einen einzigen Rückruf ausführen, nachdem eine Reihe von verschiedenen Methoden beendet wurde. Sie können das wie folgt tun:
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();
}
}
}
};
Aufrufender Code:
var waiter = createWaitRunner(done);
filterList.bindStore = waiter.getNotifier();
includeGrid.reconfigure = waiter.getNotifier(function (store) {
includeStore = store;
});
excludeGrid.reconfigure = waiter.getNotifier(function (store) {
excludeStore = store;
});
Sie können entweder nur auf die Benachrichtigungen warten oder auch andere Funktionen einschließen, die die an die Funktion übergebenen Werte verwenden. Wenn alle Methoden aufgerufen werden, dann done
ausgeführt werden.
Asynchrone Methoden der Reihe nach ausführen
Ich habe einen anderen Ansatz verwendet, wenn ich eine Reihe von asynchronen Methoden in einer Reihe aufrufen musste (wiederum in Tests). Dies ist etwas ähnlich wie etwas, das man in die Async-Bibliothek - Serie macht in etwa das Gleiche, und ich habe mir diese Bibliothek zuerst ein wenig durchgelesen, um zu sehen, ob sie das tut, was ich will. Ich denke, meine hat eine schönere API für die Arbeit mit Tests aber (und es hat Spaß gemacht, zu implementieren!).
// 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;
};
Aufrufender Code:
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;
});
});
TeilweiseAnwendenZum ist im Grunde eine umbenannte Version von Douglas Crockford die Umsetzung von Curry. Vieles von dem, womit ich arbeite, nimmt einen Rückruf als letztes Argument, so dass einfache Aufrufe auf diese Weise durchgeführt werden können, anstatt alles mit einer zusätzlichen Funktion zu verpacken.