Хабрахабр

[Перевод] Разница между асинхронной функцией и функцией, возвращающей промис

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

Взгляните на следующий фрагмент кода:

function fn(obj) { const someProp = obj.someProp return Promise.resolve(someProp)
} async function asyncFn(obj) { const someProp = obj.someProp return Promise.resolve(someProp)
} asyncFn().catch(err => console.error('Catched')) // => 'Catched'
fn().catch(err => console.error('Catched')) // => TypeError: Cannot read property 'someProp' of undefined

Как видите, обе функции имеют одно и то же тело, в котором мы пытаемся получить доступ к свойству аргумента, который не определен в обоих случаях. Единственное различие между этими двумя функциями заключается в том, что asyncFn объявляется с помощью ключевого слова async.

Это значит, что JavaScript гарантирует, что функция asnycFn вернет промис (либо выполнится успешно, либо выполнится с ошибкой), даже если в нем произошла ошибка, в нашем случае блок .catch() поймает ее.

Однако в случае с функцией fn движок еще не знает, что функция вернет промис, и поэтому выполнение кода не дойдет до блока .catch(), ошибка не будет поймана и вывалится в консоль.

Более жизненный пример

Я знаю, о чем вы сейчас думаете:

«Когда же, черт возьми, я совершу такую ошибку?»

Угадал?

Ну, давайте создадим простое приложение, которое делает именно это.

JS. Допустим, у нас есть приложение, созданное с помощью Express и MongoDB, использующее драйвер MongoDB Node. Если вы мне не доверяете, я разместил весь исходный код в этом репозитории Github, поэтому вы можете клонировать его и запустить локально, но я также продублирую весь код здесь.

Вот наш файл app.js:

// app.js 'use strict' const express = require('express')
const db = require('./db') const userModel = require('./models/user-model')
const app = express() db.connect() app.get('/users/:id', (req, res) => )) // <=== ВОТ ЭТОТ!
}) app.listen(3000, () => console.log('Server is listening'))

Внимательно посмотрите на блок .catch()! Вот где будет (не будет) происходить магия.

Файл db.js используется для подключения к базе данных mongo:

'use strict' const MongoClient = require('mongodb').MongoClient const url = 'mongodb://localhost:27017'
const dbName = 'async-promise-test' const client = new MongoClient(url) let db module.exports = { connect() { return new Promise((resolve, reject) => { client.connect(err => { if (err) return reject(err) console.log('Connected successfully to server') db = client.db(dbName) resolve(db) }) }) }, getDb() { return db }
}

И, наконец, у нас есть файл user-model.js, в котором на данный момент определена только одна функция getUserById:

// models/user-model.js 'use strict' const ObjectId = require('mongodb').ObjectId
const db = require('../db') const collectionName = 'users' module.exports = { /** * Get's a user by it's ID * @param {string} id The id of the user * @returns {Promise<Object>} The user object */ getUserById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) }
}

Если вы снова посмотрите на файл app.js, вы увидите, что при переходе по адресу localhost:3000/users/<id> мы вызываем функцию getUserById, определенную в файле user-model.js, передав в качестве запроса параметр id.

Как думаете, что произойдет дальше? Допустим, вы переходите по следующему адресу: localhost:3000/users/1.

Чтобы быть точнее, вы увидите следующую ошибку: Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters. Ну, если вы ответили: «Я увижу огромную ошибку от MongoClient» — вы были правы.

И как вы думаете, будет ли вызван блок .catch() в следующем фрагменте кода?

// app.js // ... код ... app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <=== ВОТ ЭТОТ!
}) // ... код ...

Нет. Он не будет вызван.

А что произойдет, если вы измените объявление функции на это?

module.exports = { // Обратите внимание, что ключевое слово async должно быть именно тут! async findById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) }
}

Ага, вы начинаете понимать, что к чему. Наш блок .catch() будет вызван, и мы сможем обработать пойманную ошибку и показать ее пользователю.

Вместо заключения

Я надеюсь, что для некоторых из вас эта информация оказалась полезной. Обратите внимание, что этой статьей я не пытаюсь заставить вас всегда использовать асинхронные функции — хотя они довольно крутые. У них есть свои варианты использования, но они по-прежнему являются синтаксическим сахаром над промисами.

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть