Comments 34
Ничего не мешает записать первый вызов из "глобальной" области вот так:
(async () => {
await asyncFunction();
})();
const res = (async () => {
await asyncFunction();
})();
console.log(res);
Это бессмысленно. Зачем выводить в консоль Promise?
Лучше вот так делать:
(async () => {
console.log(await asyncFunction());
})();
Хотя лично мне все-таки проще написать asyncFunction().then(console.log);
Проблема эта решается введением новой идиомы, волокон (fibers), которые по ряду причин не хотят пока добавлять в браузеры. Аналогии из других языков: go/goroutines, java/loom.
(async () => {
const [a, b, c] = await Promise.all([
import('./a.mjs'),
import('./b.mjs'),
import('./c.mjs')
]);
console.log(a, b, c);
})();
будем писать import a from './a.mjs'
import b from './b.mjs'
import c from './c.mjs'
console.log(a, b, c);
Это удобно для подгрузки динамических ресурсов. Но, например, такое уже не сделать:
function diskUsage(dir) {
return wait(fs.readdir(dir)).reduce((size, name) => {
const sub = join(dir, name);
const stat = wait(fs.stat(sub));
if (stat.isDirectory()) return size + diskUsage(sub);
else if (stat.isFile()) return size + stat.size;
else return size;
}, 0);
}
function printDiskUsage(dir) {
console.log(`${dir}: ${diskUsage(dir)}`);
}
run(() => printDiskUsage(process.cwd()))
.then(() => {}, err => { throw err; });
Добавлять сахар проще, чем вводить новую идиому в рантайм, это как сделать однопоточный js многопоточным, со всеми вытекающими.
Но проблему разделения мира на синхронную и асинхронную части никак не решить на async/await. Нельзя из синхронной функции вызывать асинхронную и работать с результатом.
Неправильный подход, вызовы будут выполнены последовательно
const authors = _.map( authorIds, id => await authorModel.fetch(id));
Этот код даже не запустится, ибо синтаксически нельзя использовать await в синхронных функциях. И вообще, что за _.map
в 2k18?
Может быть там что-нибудь типа этого подключено (prefer-lodash-method). Поясняют они это так:
When using native functions like forEach and map, it's often better to use the Lodash implementation.
This can be for performance reasons, for implicit care of edge cases (e.g. _.map over a variable that might be undefined), or for use of Lodash's shorthands.
@onError(e => -100) // invoke callback on error
async asyncFunctionCanThrowsError(error) {
await ...
throw new Error();
}
@defaultOnError(-1) // return default value (-1) on error
async defaultOnThrow(){
await ...
throw new Error();
}
Это не подходит прям для всего, но для простых случаев это более читабельно. Имхо.
Поиграться можно тут jsfiddle.net/jsbot/kw7py5r3
Очень сомнительный сахар
1) Теряется наглядность в последовательности исполнения. Сначала исполняется код снизу, потом верхняя часть.
2) Нет доступа к переменным из функции
@onError(e => fallbackFetchData(id)) // << где тут взять id?
async fetchData(id) {
// code
}
3) Если обработчик будет из нескольких строк, то выигрыша по числу кода вообще нет
async asyncFunctionCanThrowsError(error) {
try {
// code
} catch(error) {
if(someCondition) {
// some logic
}
}
}
против
@onError(error => {
if(someCondition) {
// some logic
}
})
async asyncFunctionCanThrowsError(error) {
// code
}
Хорошая попытка использовать декораторы, но работает только для однострочных функций. Если понадобится больше логики, придется переделывать все назад.
2. Конкретно это вообще не проблема, имя функции, аргументы, this и саму функцию можно передавать в callback
function onError(callback) {
...
descriptor.value = function(...args) {
...
return result.catch(e => callback(args, e)); // вот например передача аргументов
...
}
}
@onError((args, errror)=>{})
...
3. Сделайте именованную функцию, но опять же это не универсальный способ и универсального способа нет, нужно смотреть по обстоятельствам. Общий принцип, все что упрощает код хорошо, все что повышает его читабельность, тоже хорошо и наоборот.
import handleError from 'errorHandler'
@onError(handleError)
async asyncFunctionCanThrowsError(error) {
// code
}
Вот именно. Наворачиваются и добавляются усложениния, хотя можно просто обойтись try/catch
try {
} catch() {
}
это много
2. Код обработки штатной ситуации и код обработки исключения обычно логически никак не связаны, смешивание их в одном месте, усложняет чтение.
Как решить эти проблемы, можно например было бы добавить сахар в язык что то вроде
function foo() {
// logic
} catch(e) {
// error handler
}
Можно просто передать ошибку в место вызова кода, который дал сбой, и позволить обработать её там. Можно выбросить ошибку напрямую, воспользовавшись командой наподобие throw error;, что позволит использовать функцию async getBooksByAuthorWithAwait() в цепочке промисов. То есть, вызывать её можно будет, пользуясь конструкцией getBooksByAuthorWithAwait().then(...).catch(error => ...). Кроме того, можно обернуть ошибку в объект Error, что может выглядеть как throw new Error(error). Это позволит, например, при выводе сведений об ошибке в консоль, просмотреть полный стек вызовов.
Ошибку можно представить в виде отклонённого промиса, выглядит это как return Promise.reject(error). В данном случае это эквивалентно команде throw error, делать так не рекомендуется.
Тут автор уже немного перемудрил. Эти оба случая как раз эквивалентны по резултату если вообще не заключать await в try/catch.
Пробовал использовать в инициализации приложения mongodb, redis соединение к бд, роутеры, старт сервера, вроде и работает но откатил версию в коде назад на async.parallel
https://caolan.github.io/async/docs.html#parallel
Придерживаюсь правила, пока работает не трогай.
Пользуюсь только callback они быстрее, чем промисы работают. На большом количестве вызовов прирост ощутим.
Вот правильный подход к написанию такого кода:
async getBooksAndAuthor(authorId) { const bookPromise = bookModel.fetchAll(); const authorPromise = authorModel.fetch(authorId); const book = await bookPromise; const author = await authorPromise; return { author, books: books.filter(book => book.authorId === authorId), }; }
Замечу что не обязательно писать именно так, обычно пишут вот такие конструкции, так как они проще:
async getBooksAndAuthor(authorId) {
const [book, author] = await Promise.all([
bookModel.fetchAll(),
authorModel.fetch(authorId)
]);
return {
author,
books: books.filter(book => book.authorId === authorId),
};
}
Критические секции не нужны в принципе именно благодаря тому что await действует только на один фрейм вызова: если в блоке кода нет оператора await посередине, то ни один "поток" не может прервать его выполнение.
Соответственно, асинхронные семафоры и мьютексы упрощаются до чего-то такого:
class AsyncMutex
{
constructor() {
this._promise = null;
this._resolve = null;
}
async acquire() {
while (this._promise)
await this._promise;
this._promise = new Promise(resove => this._resolve = resolve);
}
release() {
if (!this._resolve)
throw "Mutex is not acquired";
this._resolve();
this._promise = this._resolve = null;
}
}
async function f() {
try {
const query1 = budjet.find({}).sort({ $natural: -1 }).limit(1);//mongoose
const result1 = await query1.exec();
console.log(result1);// тут показывет нужные данные
return result1;
} catch (e) {
console.error(e);
}
}
console.log(f());// тут Promise { <pending> }
заранее спасибо
f().then(data => console.log(data)); // 1-й вариант
console.log(await f()); // 2-й вариант
2-й вариант будет работать только в контексте вышестоящей тоже async-функции.
А чтобы не "мучиться ещё 2 дня" вслепую просто почитайте про Promise-ы и Async/await-ы. Вслепую ковыряться в них, надеясь разобраться из какого они теста, без знания теории, что за ними стоит, можно, но очень уж нерационально.
// inside another async function
async function yetAnotherFunction() {
console.log(await f());
}
// particular cause: self-invoking function
(async () => {
console.log(await f());
})();
// or
f().then((it) => {
// do everything you want with result, e.g.:
console.log(it);
});
P.S. пожалуйста, обратите внимание, что у вас функция возвращает либо массив объектов, либо undefined в зависимости от того, произошла ошибка или нет; лучше придерживаться единого формата ответа. Возможно имеет смысл обрабатывать ошибки на уровне выше.
P.P.S. если вам нужна только 1 запись, то лучше использовать findOne вместо find().limit(1)
P.P.P.S. Query в mongoose – это Promise-like объекты, поэтому вы можете использовать await прямо для них ;)
Конструкция async/await в JavaScript: сильные стороны, подводные камни и особенности использования