Pull to refresh

Comments 93

Я слышал что в v8 54 есть утечка памяти при использовании async await. ждем пока включат 55

Нужны подробности. С чего вы взяли что утечку будут фиксить только в 55 версии? Возможно уже устранили или только в браузере проявляется?

Release V8 5.5 24 октября (два дня до выхода node 7). В этом релизе представлены async\await функции.
Баг с утечкой исправлен 16 сентября, v8 5.4 был зарелизен 9 сентября
UFO just landed and posted this here
Теперь можно без babel так

Можно, но конкретно так лучше не писать.


С Promise получается короче:


require("request-promise")
  .get("http://example.com/")
  .then(content => console.log(content))

Во-вторых, с инлайновой async-функцией может возникнуть конфуз


(async function() {
    let content = await require("request-promise")
                       .get("http://example.com/");
    console.log("1");
})()
console.log("2");

В консоли будет


2
1

Что не совсем очевидно при чтении кода, хоть и понятно почему это происходит.

Касательно последнего примера: разве не в этом суть асинхронности? Честно говоря, меня забавляет, когда нодеры сначала долго рассказывают, как круто, что у них есть асинхроннсость, а потом придумывают костыли типа promises, чтобы таки писать синхронно.

Promise — это не костыль «чтобы таки писать синхронно», а способ избавиться от callback hell и структурировать асинхронный код.
UFO just landed and posted this here

Покажите мне цикл на промисах, чтоб я асихронно получал пользователя из базы далее что-делал полученными данными и запрашивал следующего пользователя.


Слабо?

Промисы позволяют применить async/await или генераторы, и получите циклы с виду синхронные.

А цикл бесконечный? Как запрашивается пользователь? Если у них id по порядку, то я просто могу пробежаться for-циклом и сделать запрос для каждого id.


Давайте немного не так: вы приведете пример кода с использованием async-функций, а мы попробуем переписать без него без потери в читаемости.
UPD. Нашел ваш коммент. Попробую что-нибудь сделать.

Это делается через рекурсию.

const usersList = getArrayOfUsersSomehow();
let usersChain = new Promise.resolve();

usersList.foreEach( (user) => {
  usersChain = usersChain
    .then( () => doSomethingWithUser());
});


Что-то подобное? Не спора ради, просто интересно.

Напишите аналог на промисах, у вас тут "независимая" обработка кажного юзера.

let user,spammers = [];
(function loop() {
    getUser().then((user) => {
        isSpammer(user).then((user) => {
            spammers.push(user);
            if (spammers.length <= 10 && user !== null)
                loop(); 
        });       
    });
}())

А зачем оно вам? async/await безусловно куда более лучшие инструменты чем promise.

Покажите мне цикл на промисах, чтоб я асихронно получал пользователя из базы далее что-делал полученными данными и запрашивал следующего пользователя.


В данном предложении содержится просто противоречие. Вы хотите получить пользователей асинхронно, а потом синхронно что-то с ними сделать. Это как? Смысл тогда какой? Асинхронно стоит выбирать, что бы потом, так же что-то с ним сделать. Возможно я не понимаю вашу мысль. Можете более детально расписать? Я тогда сделаю для вас решение.

В том то и дело что async/await выглядит как синхронный код а выполняется асинхронно.


Другими словами когда вы ждете ответа с базы вы не блокируете поток. В этот момент ваше приложение может обрабатвать другие запросы от пользователей.

Только вот с промисами код всё равно не становится синхронным, он остаётся асинхронным, только выглядит получше.
UFO just landed and posted this here
С промисом короче если у тебя один асинхронный запрос, а если у тебя 2 и больше асинхронных запроса, то на с async/await это:
(async function() {
    const request = require("request-promise")
    let someData = await request.get("http://example.com/");
    let anotherData = await request.get("http://example2.com/");
    // манипуляции с данными
    console.log(/*результат манипуляций*/);
   
})();

а с промисом:
const request = require("request-promise")
request
  .get("http://example.com/")
  .then((someData) => {
      request
         .get("http://example.com/")
         .then((anotherData) => {
              // манипуляции с данными
              console.log(/*результат манипуляций*/);
      })
   }

ничего не напоминает))

Ваш пример лучше. Мой комментарий был про то, что в статье пример использования async-функций высосан из пальца и никакой полезности не показывает.

В вашем примере для await, если каждый запрос выполняется ровно за секунду, то через какое время выполнится console.log?

Извиняюсь если допустил ошибку, но я написал бы немного по другому
const request = require("request-promise")
request
  .get("http://example.com/")
  .then((someData) => {
      return request
         .get("http://example.com/")
  })
  .then((anotherData) => {
    // манипуляции с данными
    console.log(/*результат манипуляций*/);
  }

А я бы вообще через Promise.all сделал, чтобы было параллельно


const request = require("request-promise")
Promise.all([
  request.get("http://example.com/"),
  request.get("http://example2.com/")
]).then(([a ,b]) => {
  console.log(a + b);
})

Конечно это сработает, только если второму запросу не нужны данные первого. Если нужны, то async-functions FTW.

Если я не ошибаюсь — вы потеряли someData к моменту манипуляции с данными.
А вот вариант с Promise.all — в данном случае лучший, если только второй запрос не требует информации из первого :)
Это обычный promise hell. Обещали решить с применением политиканов (politicians)
const request = require("request-promise")
request
  .get("http://example.com/")
  .then((someData) => request.get("http://example.com/"))
  .then((anotherData) => {
              // манипуляции с данными
              console.log(/*результат манипуляций*/);
      })
   

В этом и смысл — сделать код более плоским
Зачем ждать, пока выполнится первый реквест, чтобы запустить второй?

(async function() {
  const request = require("request-promise")
  const [someData, anotherData] = await Promise.all([
    request.get("http://example.com/"),
    request.get("http://example2.com/")
  ])
  // манипуляции с данными
  console.log(/*результат манипуляций*/);  
})();


const request = require("request-promise")
Promise.all([
  request.get("http://example.com/"),
  request.get("http://example2.com/")
]).then(([someData, anotherData]) => {
  // манипуляции с данными
  console.log(/*результат манипуляций*/);  
})
Затем что нужны ___последовательные___ запросы (следующий запрос зависит от предыдущего).

Чтоб не блокировать исполнение других обработчиков. Например к вашему web-серверу пришло несколько запросов.

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

Но нормальные юзкейсы со строго последовательным асинхронными операциями также существуют:
1. Операция сейва в редакторе
— подготовка превью-картинки
тут может быть параллельно еще какая-то подготовка мета-данных
— упаковка в архив
— пересылка на сервер
— опционально показ красивого(не-alert) модального окна по завершении
2. массовые операция редактирования/анализа кучи файлов
в целом операции будут идти параллельно(либо в очереди с заданным лимитом прааллельности)
но для отдельного файла будет последовательный набор шагов
— прочитать файл
— провести изменение/анализ файла
— записать файл
3. система макросов для приложения.
так как некоторые пользовательские операции вынуждены быть асинхронными, то и система макросов становится асинхронными(но строго последовательными)
Вот тут из моей практики async..await превращает код вообще в песню, по-сравнению с колбеками, или даже чистыми промисами
макросы? откуда в js макросы?
UFO just landed and posted this here
да, подразумевались макрокоманды как в офисе, либо других прикладных программах.

но в javascript-е могут быть и макросы как в C
1) Вот плагин babel-а, в котором я участвую — https://github.com/codemix/babel-plugin-macros
но тут скорее о макросах, как об инлайновых функциях
2) Также есть отдельная надстройка синтаксиса — http://sweetjs.org/
тут наверно можно гораздо шире определить макросы
Нет. С промисами вот так:
Promise.all([request.get("http://example.com/"), request.get("http://example2.com/")]).then((dataFirst, dataSecond) => {
        // манипуляции с данными
        console.log(/*результат манипуляций*/);
});
(async function() {
    const request = require("request-promise")
    let someData = await request.get("http://example.com/");
    let anotherData = await request.get("http://example2.com/");
    // манипуляции с данными
    console.log(/*результат манипуляций*/);

})();

Лучше так:


 let result = await Promise.all([request.get("http://example.com/"), request.get("http://example2.com/") ]);
по-моему здесь как раз все очевидно, async/await так и должен работать

Конечно очевидно, если медленно и внимательно читать 5 строчек кода.


Но представьте, как это будет выглядеть в реальном проекте, где кода больше


(async function() {
    let content = await require("request-promise")
                       .get("http://example.com/");
   doSomething();
   if(a !== b) {
      doSomethingElse();
   }

  // ...
  // и еще много-много кода
  // ...

  console.log("1")
})()
console.log("2");

Пока дочитаешь до конца, можно потерять контекст и ошибиться. А с Promise и коллбеками граница синхронного и асинхронного кода четче и понятнее.

А можете привести пример кода? Без него непонятно, что вы имеете в виду.

let user,spammers = [];
do {
    user = await getUser();
    if (await isSpammer(user)) {
        spammers.push(user)
    }
} while (spammers.length <= 10 && user !== null);
console.log(spammers)

Ну и все это, конечно, внутри асихронной функции.

function collectSpammers(spammers) {
   if(spammers.length > 10) {
     return spammers;
   }
   return getUser()
     .then(user => {
       if(!user) {
         return spammers;
       }
       return isSpammer(user).then(spammer => {
          if(spammer) {
             spamers.push(users);
          }
          return spammers;
       });
     })
     .then(() => collectSpammers(spammers))
}

collectSpammers([]).then(spammers => console.log(spammers))

Согласен, получилось не так аккуратно как с async, но ведь реализуемо! Сомневаюсь, что нужно писать такой код регулярно, и поэтому async так уж необходим в стандарте

Если spammers.length <= 10 && user == null он вроде бы продолжит выполняться

Почему вы так считаете? Есть же проверка сразу после getUser.

getUser().then(u => { if (!u) return spammers }) Зарезолвится с массивом spammers, потом вы вызываете .then(() => collectSpammers(spammers)), и процесс зациклился, пока getUser() возвращает null следующий в цепочке then будет вызывать collectSpammers

Действительно, спасибо за замечание! Поправил:


function collectSpammers(spammers) {
   if(spammers.length > 10) {
     return spammers;
   }
   return getUser().then(user => {
     if(!user) {
       return spammers;
     }
     return isSpammer(user)
       .then(spammer => {
         if(spammer) {
           spamers.push(users);
         }
         return spammers;
       })
       .then(() => collectSpammers(spammers));
   })
}

collectSpammers([]).then(spammers => console.log(spammers))
Ок, раз уж пошла такая пьянка, добавлю свой вариант :)
function collectSpammers(n) {
  const spammers = [];
  
  return Array(n).fill(0).reduce(m => m
    .then(() => getUser()
      .then(function check(u) {
        if (u === null) return Promise.reject();
        
        return isSpammer(u).then(spammer => { 
          if (spammer) spammers.push(u);
          else return getUser().then(check);
        });
      })), Promise.resolve()).then(() => spammers, () => spammers);
}

collectSpammers(10).then(spammers => console.log(spammers));
function collectSpammers(_users) {
  return _users.reduce((promise, _user) => {
    return promise.then((prevUsers = []) => {
      const user = User.findById(_user);
      return Promise.all([...prevUsers, user]);
    })
  }, Promise.resolve());
};
Mongoose Model#findById возвращает Promise если не передан callback

Тут немного другая задача решается

А если вам нужно проверить не случилось ли ошибки при получении данных из базы
try {
user = await getUser();
} catch (err) {
// Error handling
}
Вы представляете во что превратиться Ваш код?


async/wait это отличная конструкция для написания человек-читаемого и легко понимаемого кода. Потому что код выглядит как синхронный а виполняется асинхронно.


Ваше заявление "С Promise получается короче" работает только для примитивных конструкций, для более сложных это не всегда справедливо

А если вам нужно проверить не случилось ли ошибки при получении данных из базы
Вы представляете во что превратиться Ваш код?

Примерно в то же самое, во что и ваш. Добавится пара конструкций.


Ваше заявление "С Promise получается короче" работает только для примитивных конструкций

Согласен. Но в статье вы привели именно такую конструкцию, на что я и обратил внимание. Чтобы показать хорошие стороны async можно было сразу привести пример про спаммеров, а не тривиальный request.

let spammers = [];

getUser()
    .catch(onGetUserErrorHandler)
    .then(getSpammer)
    .then(() => console.log(spammers));

function getSpammer(user) {

    if (user === null) {
        return;
    }

    if (spammers.length === 10) {
        return;
    }

    return isSpammer(user)
        .then((isSpammer) => isSpammer && spammers.push(user))
        .catch(onSpammerCheckErrorHandler)
        .then(getUser)
        .catch(onGetUserErrorHandler)
        .then(getSpammer);
}


then после catch не правильно, если, конечно, не хотите попасть в оба.

если возникнет ошибка с одним юзером, то перейдем к следующему, а не завершим работу
```
var user, spammers = [];

function load () {
return getUser().then(function (user) {
if (isSpammer(user)) {
spammers.push(user);
}
return user;
});
}
var array = _.map(_.range(0, 10), function () {
return load;
})
utils.sequence(array).then(function () {
console.log(spammers);
});
```
Для debian-based дистрибутивов можно установить так:

curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
sudo apt-get install -y nodejs

Доку пока ещё не обновили, может и под другие дистрибутивы уже можно установить.
Ещё можно использовать NVM:
nvm install v7.0.0
nvm alias default v7.0.0

Можно даже так (если фиксирование минорной версии не критично):


nvm install v7 // поставится последняя доступная v7
nvm alias default v7 // дефолтной всегда будет самая свежая из установленных 7-ок, не нужно будет постоянно подправлять минорные версии после обновления

Скажите пожалуйста, как вы решаете проблемы с nvm и подобными библиотеками для установки библиотек, когда например ты установил своему пользователю какую-то версию node.js, а потом из под банального sudo {cmd} эта нода уже недоступна? Та же проблема с rvm, к примеру. Или другой случай: надо запускать по крону какой-то баш скрипт, которому тоже эта Node.js нужна. Там тоже обычно приходится немного извращаться, подключая в начале скрипта нужные файлы из папки nvm.


Ставить глобально через какой-нибудь apt по-моему удобнее всё-таки, чем таким образом. Хотя и менее гибко.

Ни разу не приходилось запускать node с sudo за время использования nvm, а до этого приходилось лишь пару раз. Но, если бы пришлось, то скорее всего прописал бы полный путь до бинарника (sudo ~<username>/.nvm/versions/node/<version>/bin/node). Да, неудобно, но если это не разовый запуск (например крон), то можно создать симлинк в /usr/bin или создать алиас или скрипт:


#!/path/to/node

console.log('Hello World!')

Сейчас вообще стараюсь не запускать проекты на хост-машине напрямую. Толкаю всё в докер, а с хост машины дёргается docker-compose exec some-periodic-task node /src/task.js — криво, неудобно, но лучше не придумал.

Неудобен такой подход, с прямой ссылкой на какой-то бинарник node.js в nvm. Обновишь ноду — и все скрипты исправлять свои? :) Если идти такой дорогой, то к баш-скрипту лучше подключать код загрузки .nvm из .bashrc:


export NVM_DIR="/home/{username}/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm

Тогда будет загружаться текущая активная Node.js.




Возвращаясь к глобальной установке установщиков библиотек. На тему Node.js я могу сам за себя ответить, ибо нашёл способ установить nvm глобально на машине:


wget -qO- https://raw.githubusercontent.com/xtuple/nvm/master/install.sh | sudo bash


Но блин. В мире-то не только NVM существует… Для каждой библиотеки такой форк искать на гитхабе? :) В общем, для себя я пока ничего лучше какого-то свеженького ppa или последнего дистрибутива своей ОС не нашёл.

xtuple/nvm

Эм. Вообще-то, апстрим creationix/nvm, зачем вы устанавливаете старый форк какой-то компании?


В nvm было довольно много изменений с тех пор.

Ну дык будьте хоть немножечко более внимательны, я достаточно разъяснил всё в своём сообщении. Это не просто форк, а изменённый инсталлер. Почитайте хоть описание к нему, написано же всё.

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

А в стандарте уже прописано, как резолвить пути в этих операторах? Т.е. если мы напишем:


import { foo } from 'foo';
import { bar } from './bar';

откуда будут импортированы эти объекты?


С require всё понятно — это чисто нодовская функция, она завязана на систему модулей ноды и node_modules.


Но стандарт ecma не должен завязываться на какую-то конкретную реализацию и должен как-то предусмотреть различные платформы. Ведь в браузере "нет node_modules".

гугл на одном из недавних выступлений сказали, что пока этого в движке V8 нет

Так а разве в стандарте есть описание (хотя бы черновик) того, как именно нужно будет резолвить модули? Пока определились только с синтаксисом импортов/экспортов. А пока стандарта нет, в движок нечего добавлять.

UFO just landed and posted this here

Резолвить по тем же правилам, что и пути в require. Сделали бы хоть простейшую поддержку в виде трансформации в require/module.exports.

Можно, конечно, включить это в ноду напрямую. Люди начнут писать под ноду программы, используя нативные импорты (т.е. в npm-пакет попадёт не common-модуль (с require и module.exports), а es6-модуль с import/export).


А потом примут стандарт, и окажется, что резолв модулей как в require не совсем совместим с принятым стандартом. Что тогда делать? Уже написанные программы не будут работать на новых версиях ноды, новые программы не будут работать на тех версиях ноды, где была неправильная нативная поддержка модулей. Представьте, как весело станет управлять зависимостями, если часть из них написаны по-старой схеме, часть по-новой.


Именно для этого и есть babel. Добавить или удалить плагин в бабеле просто, а вот менять версию ноды уже тяжелее. Когда ситуация с модулями прояснится, тогда и нужно будет начинать внедрять, но не раньше.

Для трансформаций есть babel, он хорошо делает свою работу, зачем дублировать эту функциональность в Node.js, при том, что с большой вероятностью её потом нужно будет выпиливать?

Да в том-то и дело, что хотелось бы без babel. Он, конечно, крутой, но все-таки отсутствие компиляции круче, чем самая крутая компиляция.

Сделали бы хоть простейшую поддержку в виде трансформации в require/module.exports.


А это не компиляция будет?

Технически, конечно, компиляция (или интерпретация). Компьютеры пока не научились джаваскрипт исполнять непосредственно, поэтому, совсем без компиляции/интерпретации не обойтись, очевидно :)


Я имел ввиду компиляцию джаваскрипта перед запуском на Node.

Понимаю, что сейчас будут дико минусовать карму, но: Зачем?((

1. Зачем вводить функционал до того, как он появится в стандарте (это еще draft на es2017)?
2. Зачем вводить async/await на уровне языка, учитывая, что это всего лишь сахар для промисов?
вот завтра окажется, что «async/await» — это плохо, а поддерживать эту фичу придется до конца жизни ES.
UFO just landed and posted this here
Цель благородная, но я к тому, что завтра появится async/await hell и быть беде. Если Promise API можно просто выкинуть и забыть (заменить на что-то новое), то async/await из спецификации уже не выкинешь.

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

Хороший поинт, инженерный подход, но к сожалению вся суть развития EcmaScript относится к "деланию кода красивее", в отличии от допустим TypeScript.

async await впервые появился в c# в 2012 году и за 4 года не заработал себе плохой репутации.
да и вообще, как вы себе представляете async/await hell?
можно пример?

> как вы себе представляете async/await hell?

Как жуткое хитросплетение синхронных и асинхронных функций.
Как асинхронные функции, которые будут отдаваться наружу как результат выполнения
… и await await func() как следствие.
Как асинхронные функции в качестве параметров для тех же промисов или еще чего-то.

— проблема все та же = незнание как написать хорошо, порождает hell.

> async await впервые появился в c# в 2012 году и за 4 года не заработал себе плохой репутации.

Я могу ошибаться, но проблем с этим подходом не на много меньше, чем с обычным многопоточным программированием, просто эти проблемы сделали неявными.
Все те же await await func() и манипуляции с контекстом.

Да эти все «минные тропы» уже через месяц проходятся довольно просто, но не стоит забывать, что месяц — это у хорошего специалиста, который уже познал горя с многопоточностью.

Да и в C# вам множество тулов поможет и подскажет где вы не правы. В то время, как в ES это можно будет узнать только в рантайме и порою даже только на продакшене.

ps: я за развитие языка, но я против добавления Сахара ради сахара.
Хотелось бы узнать больше об этих минных тропах.
с обычными callback-ами мне доводилось сталкиваться с тремя проблемными анти-патернами:
1) асинхронная функция содержит некоторое количество синхронного кода
если он упадет, асинхронная функция никогда не вернет результата.
весьма пакостный для отладки случай.
— чистые промисы решают эту проблему не полностью, если использовать их как попало
2) асинхронная функция делает что-то еще после вызова callback
— странно что-то делать после того как вернул результат.
— этим что-то может оказаться повторный вызов callback(например сразу после if-а), что приводит к двойному старту последующего кода
— но для этой ошибки есть правило в eslint)
— промисы решают эту проблему
3) асинхронная функция вызывает callback на самом деле синхронно(например данные из кэша)
— может порождать дивные стектрейсы в случае падения в колбеке(захватится уже «выполненная» функция не имеющая отношения к падению)
— при попытке решать первую проблему try..catch-ами, приводит ко второй проблеме — err-callback будет вызван еще раз
— логика сложнее в отладке, так как нарушен контракт
— промисы решают эту проблему

Использование async..await автоматически фиксит все 3 проблемы, даже не допуская возможности так ошибиться
И единственный встреченный минус: стектрейсы при отладке все еще не синхронные)
Ну и дополнены обертками от babel

Поэтому интересуют какие проблемы сохранили/создали async..await-ы?
Особенно в контексте того что, в C# есть способы ранней проверки анти-патернов.
Я бы с удовольствием написал бы пару правил для статического анализа в eslint

Это не драфт, предложение находится в Stage 3 ("Candidate"), в следующем месяце будет частью стандарта Stage 4 ("Finished").

Потому что, по сути, это единственный путь рождения и проверки немертворожденных стандартов. А по поводу поддержки, это не обязательно, можно объявить deprecated. Поэтому и запускается не в LTS-версии.
Node.js 7.0.0 зарелизился. Встречайте async/await без babel

Включается это все ключем --harmony.

Эээ. Напомню ещё раз: --harmony в продакшне использовать не стоит, и привязываться к его фичам — тоже.


Пока что рекомендованный способ сделать себе async/await — это таки babel.


98% возможностей JavaScript, определённых в спецификации ES2015 (ES6)

Это и в v6 LTS было. Причём отставшиеся 2% — PTC.

Sign up to leave a comment.

Articles