Pull to refresh

Comments 45

У меня велосипед получился точно такой же. С точностью до названий функций. А переименую-ка я их как у Вас...

Кто нибудь скажет мне чем promise лучше «классического ajax»?
Тем же, чем мягкое лучше теплого.

На самом деле, профит от промизов в том, что это асинхронный компонуемый примитив. Они легко выстраиваются параллельно или последовательно или в любой комбинации, да еще обработка ошибок довольно удобная.

Справедливости ради, если не лепить анонимные функции, а грамотно все декомпозировать и разносить по модулям, то и без промисов все выглялит вполне прилично. Но промизы действительно легко компонуются.

Последняя версия кода с отдельными функциями сморится приятно, но как по мне изначальная версия гораздо понятнее.
А почему нельзя, например


    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() {}


Никаких пирамид, функции можно делать чистыми, на мой взгляд более нагляден порядок выполнения. Возможно известная практика, на открытие Америки не претендую.
Какие же они чистые, если в них вызывается асинхронный код и они при этом сами ничего не возвращают
А я и не говорил, что они чистые.
… и вспомните про новые, более костыльные костыли, нежели эти ваши асинхронные костыли.
Первый пример (который без промисов) точно будет работать так, как задумано? Ведь fs.readdir асинхронна, так что помешает console.log выполниться сразу после неё, до того, как управление попадёт в коллбэк? Тогда в логе окажется пустой массив.
Ну и помимо указанных способов можно делать так, как обычно делают все новички в 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++ утечки памяти. Вроде и инструментов куча, чтоб этого избежать, и подходов множество, а всё равно на мало-мальски большом проекте оно проявляется.
да `buffers` будет пустой, он там для того что бы показать куда попали данные, иначе непонятно что делает код, так как это даже не функция

поименовать колбеки, кстати полезно не только новичкам, но и для отладки, особенно если код был пропущен через babel
Но вообще лапша в коде в JS такая же неизбежность, как в C++ утечки памяти.

Ну не знаю — ни в js ни в typescript не наблюдаю ни лапши ни пирамид. А еще в копилку перечисленного — @autobind декоратор, помогает при использовании методов класса в качестве коллбэков.

объясните чем хорошо @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)


достаточно встроенного сахара и не нужно подключать кучу дополнительных либ

можно. Но если метод класса изначально предназначен для использования коллбэком (на всякие addListener) — то проще поставить один раз декоратор на метод в описании и не парится каждый раз по поводу того как его добавлять в хуки.

Но вообще лапша в коде в 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 живем.
Спасибо, друзья! Реально — интересно. Ощущение, что «на чужой раён» забрел, но любопытно же )
Поскольку многопоточности в JS нету, а вешать весь браузер на каждый ajax-запрос никому не хочется, выкручиваемся асинхронностью — запрашиваем операцию и передаем ссылку на функцию, которую надо вызвать по ее окончании.
Это понятно (на LUA похоже, там сплошь и рядом), но где, я извиняюсь, в Вашей ассинхронности таймауты? Про точки синхронизации даже не спрашиваю пока. Неужели сам браузер их определяет?

Да, их определяет среда выполнения. Если вы сделаете setTimeout(myFunc, 1000), нет никакой гарантии, что myFunc выполнится через тысячу миллисекунд. Если в этот момент браузер (или nodejs) будет занят чем-то другим (например, обработкой какого-то события от пользователя), то «пусть весь мир подождет». Есть некоторый стэк вызовов, если он не пуст, то наш коллбэк вызовется, только когда он очистится.

UFO just landed and posted this here

Кстати, первый код в посте и в комментариях — препаршивый, так как размазывает обработку ошибок по всему коду, вместо того, чтобы пробрасывать ошибку наверх и обрабатывать ошибки в одном месте. Вариант с промисами этого бага лишён.


Правильная обработка ошибок в ноде выглядит так:


var myAsyncFunction = ( ...args , done ) => {
    otherAsyncFunction( ( error, result1 ) => {
        if( error ) return done( error )

        // do something with result1 and generate result2

        done( null , result2 )
    } )
}

Вы бы не минусы ставили, а сходили по ссылке и посмотрели правильные реализации на разных подходах.

Кстати, я там запилил и на генераторах и на асинкавайте реализации. Можете сравнить.

Вот спасибо. Как раз сейчас с колбэками в JS разбираюсь.
для генераторов заюзать что-то типа CO и будет тоже самое что с и async/await… это к слову про «это самый красивый подход»… А разве async/await поддерживается в NodeJS?
то есть пирамиду из `generator.next().value.then` замели под ковер под названием СО и сказали, что это тру :)
Ну если вы используете генераторы, то вы явно будете использовать «СО»… это можно рассматривать как подключение любого стороннего модуля, так что да — это нормально, ИМХО. А как вы рассуждаете можно и кишки async/await наружу вытащить =)
согласен если статья приводит пример с использованием `bluebird` то пример с `CO` ничем не хуже.

а вот кишки async/await написаны на С++ так что сравнивать их можно только с чем-то подобным, например с https://github.com/SyntheticSemantics/ems
Используя транспайлеры(например babel) вполне поддерживается, причем практически в любом окружении.
Более того, с помощью babel-а, можно использовать и кучу других синтаксических плюшек из будущих стандартов и даже то, что не планируется к стандартизации в языке(например react, flow)
не могу смотреть как юзается переменная «file» в первой версии кода, — какое по вашему у неё будет значение во время выполнения fs.readFile(file, ...)? остальные версии вообще не понял, должно быть старею ))
не пойму почему так не написать:
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://pyha.ru/forum/topic/9319.1
Sign up to leave a comment.