Примите асинхронную природу 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);
});
Здесь я вызываю что-то, что загружает extjs 'store' и ждет, пока хранилище содержит что-то перед продолжением (beforeEach - это фреймворк для тестирования жасмина).
Дождаться завершения нескольких вещей
Еще одна вещь, которую мне нужно было сделать, - запустить один обратный вызов после завершения загрузки различных методов. Вы можете сделать это следующим образом:
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 - серия делает примерно то же самое, и я сначала немного прочитал эту библиотеку, чтобы посмотреть, получилось ли то, что я хотел. Я думаю, что у меня есть более приятный API для работы с тестами (+ это было интересно реализовать!).
//provides a context for running asyncronous methods syncronously
//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
//eg 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 здесь в основном переименованная версия реализации Curry Дуга Крокфорда. Многое из того, с чем я работаю, принимает в качестве последнего аргумента обратный вызов, поэтому простые вызовы можно выполнять, как это, вместо того, чтобы оборачивать все с помощью дополнительной функции.
Надеюсь, некоторые идеи там могут быть полезны для людей.
В настоящее время нет прямого способа сделать это в MEF, но команда MEF рассматривает возможность поддержки этого в v.Next. По сути, вы хотите создать несколько экземпляров одной и той же реализации, что традиционно делается с использованием шаблона Factory. Итак, один из подходов, который вы могли бы использовать, выглядит примерно так:
public interface IRandomNumberGeneratorFactory
{
IRandomNumberGenerator CreateGenerator(int seed);
}
[Export(typeof(IRandomNumberGeneratorFactory))]
public class ConcreateRNGFactory : IRandomNumberGeneratorFactory
{
public IRandomNumberGenerator CreateGenerator(int seed)
{
return new ConcreateRNG(seed);
}
}
public class Consumer
{
[Import(typeof(IRandomNumberGeneratorFactory))]
private IRandomNumberGeneratorFactory generatorFactory;
private List<IRandomNumberGenerator> generators;
private List<double> seeds;
public Consumer()
{
generators = new List<IRandomNumberGenerator>();
seeds = new List<double>(new[] {1.0, 2.0, 3.0});
foreach(var seed in seeds)
{
generators.Add(generatorFactory.CreateGenerator(seed));
}
}
}
Я считаю, что для этого и предназначена функция Ленивый экспорт . На этой странице:
[Import]
public Export<IMessageSender> Sender { get; set; }
В этом случае вы соглашаетесь отложить создание этого экземпляра до тех пор, пока вам действительно не понадобится экземпляр реализации. Для запроса экземпляра используйте метод [Export.GetExportedObject ()]. Обратите внимание, что этот метод никогда не будет действовать как фабрика реализаций T, поэтому при его многократном вызове будет возвращен один и тот же экземпляр объекта, возвращенный при первом вызове.
Предварительный просмотр MEF 8 имеет экспериментальную поддержку, хотя еще не включен в System.ComponentModel.Composel.dll
. Смотрите этот пост в блоге для получения более подробной информации.
Вам нужно будет скачать исходники MEF и собрать решение. В папке Samples\DynamicInstantiation
находится сборка Microsoft.ComponentModel.Composition.DynamicInstantiation.dll
. Добавьте ссылку на эту сборку и добавьте провайдер динамической инстанцирования в свой контейнер следующим образом:
var catalog = new DirectoryCatalog(".");
var dynamicInstantiationProvider = new DynamicInstantiationProvider();
var container = new CompositionContainer(catalog, dynamicInstantiationProvider);
dynamicInstantiationProvider.SourceProvider = container;
Теперь ваши части смогут импортировать PartCreator
, если им нужно динамически создавать Foo
части. Преимуществом по сравнению с написанием собственного заводского класса является то, что это прозрачно позаботится об импорте Foo
, и импорте и т.д..
редактирование :
PartCreator
было переименовано в ExportFactory
, но оно включено только в серебряное издание. ExportFactory
включено в настольное издание. Так что ExportFactory
, вероятно, будет частью следующей версии .NET фреймворка после .NET 4.0.