Цель работы: научиться оборачивать интерфейсы и добиться такой универсальности
обертки, чтобы не зависеть от того, какой именно интерфейс она оборачивает.
В качестве примера обернута одна функция setTimeout, а в качестве задания мы
обернем целый интерфейс работы с файловой системой - библиотеку fs.
framework.js- кусочек фреймворка, демонстрирующий обертку (wrapper)application.js- часть приложения для демонстрации обертки
Из командной строки пишем node application, а потом node framework,
сравниваем вывод, смотрим код framework.js и application.js, понимаем
как работает обертка вокруг setTimeout
- Нужно изучить, как обернут таймер.
По аналогии с оберткой таймера нужно сделать обертку вокруг модуля fs.
Все его функции нужно пройти в цикле
for (const key in fs) { ... }и заменить на свою функцию. При помощи замыкания эта функция должна стать универсальной прослойкой для всех функций библиотеки fs. Смысл обертки - логировать все вызовы к файловой системе в файл, с указанием времени, имени функции, ее аргументов, а если функция имеет еще и callback, то нужно перехватывать и его, тоже логируя в файл момент, вызова callback. Это задание можно разбить на несколько шагов. - Удаляем из
application.jsвызов таймера и оставляем там только код:
const fileName = './README.md';
console.log('Application going to read ' + fileName);
fs.readFile(fileName, (err, src) => {
console.log('File ' + fileName + ' size ' + src.length);
});Это пример работы с файлом. И мы будем изменять поведение этого кода.
Убираем из framework.js обертку таймера и пробрасываем fs в приложение.
Теперь запускаем node framework и убеждаемся, что файл считывается и
выводится его длина.
3. Теперь пишем функцию cloneInterface(interfaceName) для копирования всех
ключей из библиотеки fs в новый интерфейс и передаем в песочницу не исходный
fs, а склонированный. Пример функции клонирования:
const cloneInterface = (anInterface) => {
const clone = {};
for (const key in anInterface) {
clone[key] = anInterface[key];
}
return clone;
};- Пишем функцию
wrapFunction(fnName, fn)которая оборачивает функциюfnи возвращает функцию-замыкание отwrapper. Замыкание, это ссылка на копию функцииwrapper, которая замкнута на контекстwrapFunction. Таким образом мы применяем функциональное наследование и порождаем такой вариантwrapper, который видит параметрыfnNameи 'fn' отwrapFunction. Мы полностью передаем все аргументы в функцию fn:
const wrapFunction = (fnName, fn) => (...args) => {
console.log('Call: ' + fnName);
console.dir(args);
return fn(...args);
}- Определяем, есть ли среди аргументов
callback, он всегда последний в массиве аргументов и его типfunction. Еслиcallbackесть, то вместо него передаем свою функцию, которая логирует все аргументы и вызывает настоящийcallback. - Теперь можно из
application.jsиспользовать другие функцииfsи убедиться, что все они обернуты. - Добавляем таймеры в
application.jsи на таймерах работаем с файлами, а изframework.jsсобираем статистику работы с файлами и выводим ее каждые 30 секунд. Например, можно собирать несколько из этих параметров
- количество обращений к функциям,
- количество колбэков,
- среднюю скорость завершения функций,
- среднюю скорость возвращения колбеков,
- общий объем прочитанных с диска данных,
- общий объем записанных данных,
- среднюю скорость чтения и записи, и т.д.
Сохраните наработки этой лабораторной работы, они понадобятся для выполнения следующих работ, в частности, работы по передаче вызовов в другой процесс и другой сервер. Это позволит распределить исполнение приложения.
- Попробуйте подставить в качестве файловой системы свою структуру из памяти,
и прочитать из нее при помощи
fs.readFile, потом записать файл, создать и удалить. Пример структуры:
const virtualFs = {
folder: {
subfolder: {
file1: 'File content',
file2: 'Another file content'
},
},
notes: {
myToDos: 'Refactor projects, Prepare tests',
meetings: 'Meet thoughts at 10:00 walking along garden'
}
};- Реализуйте кеширование файловых операций в памяти.
- Напишите аналогичный пример на другом языке программирования.