Comments 45
У меня велосипед получился точно такой же. С точностью до названий функций. А переименую-ка я их как у Вас...
На самом деле, профит от промизов в том, что это асинхронный компонуемый примитив. Они легко выстраиваются параллельно или последовательно или в любой комбинации, да еще обработка ошибок довольно удобная.
Справедливости ради, если не лепить анонимные функции, а грамотно все декомпозировать и разносить по модулям, то и без промисов все выглялит вполне прилично. Но промизы действительно легко компонуются.
Последняя версия кода с отдельными функциями сморится приятно, но как по мне изначальная версия гораздо понятнее.
А почему нельзя, например
if (error) {
console.error(error);
} else {
for (var i = 0, j = files.length; i < j; i++) {
console.log(files[i]);
}
}
Заменить на
if (error) {
console.error(error);
return;
}
for (var i = 0, j = files.length; i < j; i++) {
console.log(files[i]);
}
?
В примере все условия внутри лямбд.
fs.readdir(__dirname, function(error1, files) {
if (error1) {
console.error(error1);
return;
}
var fs_stat = function(error2, stats) {
if (error2) {
console.error(error2);
return;
}
if (!stats.isFile()) {
return;
}
fs.readFile(file, function(error3, buffer) {
if (error3) {
console.error(error3);
return;
}
buffers.push(buffer);
});
};
for (var i = 0, j = files.length; i < j; i++) {
var file = path.join(__dirname, files[i]);
fs.stat(file, fs_stat);
}
});
По-моему вполне читаемо, и не размашисто.
Проблема в том, что на маке с ретиной порой заканчивается место под пробелы
Приятно, что индустрия после первого шага «Оптимизировать дорого, докупим серверов» ещё не сделало второго «Если код содержит божественные объекты и избыточную вложенность, просто купить монитор с диагональю побольше»
$.post('/url', {data: 'data'}, onResponseCallback1);
function onResponseCallback1() {
$.post('/url', {data: 'data'}, onResponseCallback2);
}
function onResponseCallback2() {
//...
}
На ноде с промисами:
Promise.resolve()
.then(onResponseCallback1)
.then(onResponseCallback2)
.catch(onError)
function onResponseCallback1(data) {
return data;
}
function onResponseCallback2(data) {
return data;
}
// Для обработки ошибок
function onError() {}
Никаких пирамид, функции можно делать чистыми, на мой взгляд более нагляден порядок выполнения. Возможно известная практика, на открытие Америки не претендую.
Ну и помимо указанных способов можно делать так, как обычно делают все новички в JS, впервые столкнувшиеся с асинхронной лапшой — т.е. поименовать все коллбэки, расположив их друг за другом (выше уже сказали о такой возможности):
fs.readdir(__dirname, processFiles );
function processFiles(error1, files) {
if (error1) {
console.error(error1);
} else {
beginReading(files);
};
}
function beginReading(files) {
for (var i = 0, j = files.length; i < j; i++) {
var file = path.join(__dirname, files[i]);
fs.stat(file, getFileStats);
}
}
function getFileStats(error2, stats) {
if (error2) {
console.error(error2);
} else if (stats.isFile()) {
fs.readFile(file, fileReaded);
}
};
function fileReaded(error3, buffer) {
if (error3) {
console.error(error3);
} else {
buffers.push(buffer);
}
}
Но вообще лапша в коде в JS такая же неизбежность, как в C++ утечки памяти. Вроде и инструментов куча, чтоб этого избежать, и подходов множество, а всё равно на мало-мальски большом проекте оно проявляется.
Но вообще лапша в коде в JS такая же неизбежность, как в C++ утечки памяти.
Ну не знаю — ни в js ни в typescript не наблюдаю ни лапши ни пирамид. А еще в копилку перечисленного — @autobind декоратор, помогает при использовании методов класса в качестве коллбэков.
однако в текущей реализации он ущербен https://github.com/jayphelps/core-decorators.js/issues/76
а для того что бы использовать его в лоб достаточно двойного двоеточия `::obj.method` (вот же ж этот камент про пхп сверху)
Вы:
однако в текущей реализации он ущербен
@autobind doesn't work properly when calling super from the parent's parent's parent.
@autobind
class A {
method() {
console.log(this.test);
}
}
Я:
@autobind декоратор, помогает при использовании методов класса в качестве коллбэков.
Так то никто Вам не мешает сальто в прыжке с переворотом делать, просто я то не об этом говорил.
promise.then(::obj.method)
достаточно встроенного сахара и не нужно подключать кучу дополнительных либ
Но вообще лапша в коде в JS такая же неизбежность, как в C++ утечки памяти.Ну, не знаю — у меня в таком знаете ли нефиговом проекте на С++ (24*7) утечек памяти не наблюдается
А вообще, это же обычная прогулка через «тернии к звездам» когда успешный сценарий один, а возможных ошибок мильон:
bool bOk = false;
do{
if(что-то не так) break;
if(что-то не так) break;
if(что-то не так) break;
if(что-то не так) break;
if(что-то не так) break;
if(что-то не так) break;
if(что-то не так) break;
if(что-то не так) break;
bOk = true;
}while(0);
if(bOk) все получилось;
else обрабатываем ошибку;
Коды ошибок и выводы в лог — добавляются по вкусу.
В JS так нельзя?
Поскольку многопоточности в JS нету, а вешать весь браузер на каждый ajax-запрос никому не хочется, выкручиваемся асинхронностью — запрашиваем операцию и передаем ссылку на функцию, которую надо вызвать по ее окончании. И вот вроде бы и параллельность есть и программист себе последнюю ногу не отстрелил (ох уж эти программисты, на что только не идут, лишь бы не чинить баг в race condition).
Вот только если наспех код херачить, то вот такая «пирамидка» получается.
Поскольку многопоточности в JS нету, а вешать весь браузер на каждый ajax-запрос никому не хочется, выкручиваемся асинхронностью — запрашиваем операцию и передаем ссылку на функцию, которую надо вызвать по ее окончании.Это понятно (на LUA похоже, там сплошь и рядом), но где, я извиняюсь, в Вашей ассинхронности таймауты? Про точки синхронизации даже не спрашиваю пока. Неужели сам браузер их определяет?
Да, их определяет среда выполнения. Если вы сделаете setTimeout(myFunc, 1000)
, нет никакой гарантии, что myFunc выполнится через тысячу миллисекунд. Если в этот момент браузер (или nodejs) будет занят чем-то другим (например, обработкой какого-то события от пользователя), то «пусть весь мир подождет». Есть некоторый стэк вызовов, если он не пуст, то наш коллбэк вызовется, только когда он очистится.
Информация для медитации: https://github.com/nin-jin/async-js/
Пулреквесты с реализацией на генераторах и асинкавайте приветствуются :-)
Кстати, первый код в посте и в комментариях — препаршивый, так как размазывает обработку ошибок по всему коду, вместо того, чтобы пробрасывать ошибку наверх и обрабатывать ошибки в одном месте. Вариант с промисами этого бага лишён.
Правильная обработка ошибок в ноде выглядит так:
var myAsyncFunction = ( ...args , done ) => {
otherAsyncFunction( ( error, result1 ) => {
if( error ) return done( error )
// do something with result1 and generate result2
done( null , result2 )
} )
}
Вы бы не минусы ставили, а сходили по ссылке и посмотрели правильные реализации на разных подходах.
Кстати, я там запилил и на генераторах и на асинкавайте реализации. Можете сравнить.
Более того, с помощью babel-а, можно использовать и кучу других синтаксических плюшек из будущих стандартов и даже то, что не планируется к стандартизации в языке(например react, flow)
не пойму почему так не написать:
function isNotError(err) {
if(!err)
return true;
console.error(err);
return false;
}
function onReadDir(err, files) {
if(isNotError(err)) {
for (var i = 0; i < files.length; i++)
readFile(path.join(__dirname, files[i]));
}
}
function readFile(file) {
function onStat(err, stats) {
if(isNotError(err) && stats.isFile())
fs.readFile(file, onRead);
}
function onRead(err, buffer) {
if(isNotError(err))
buffers.push(buffer);
}
fs.stat(file, onStat);
}
fs.readdir(__dirname, onReadDir);
после вот этого?
function isNotError(err) {
if(!err)
return true;
console.error(err);
return false;
}
но признаю, с «не могу смотреть» я, пожалуй, перегнул, — напротив, мне просто бальзам на душу, когда кто-то начинает устраивать замыкания внутри цикла, за что получает трудно уловимый баг, — тогда я смотрю на это высунув язык от радости и приговариваю: «я же говорил, что не надо использовать анонимные функции где попало».
У вас async/await код больно страшный. К примеру, в функции checkItems
последние catch
и then
на одном уровне, хотя первое относится к fs.stat
, а второе к Promise.all
. Плюс использовать исключения для управления потоком не очень хорошо
К тому же код в примерах не эквивалентный, в promise-версии файлы фильтруются и читаются сразу, а в async-версии сначала получается список файлов, потом читается по списку — отсюда два использования Promise.all
У меня с async получилось так:
async function readFile(file) {
const stat = await promisify(fs.stat, [path.join(__dirname, file)]);
if (!stat.isFile()) {
console.error("Not a file!");
return null;
}
return await promisify(fs.readFile, [file]);
}
async function main() {
const items = await promisify(fs.readdir, [__dirname]);
const buffersOrNull = await Promise.all(items.map(readFile));
return buffersOrNull.filter(buffer => buffer !== null);
}
На этот раз это даже не перевод: http://mabp.kiev.ua/2015/12/24/pyramid-of-death/
Как разравнять Пирамиду смерти