Существует небольшая, но довольно важная разница между функцией, которая просто возвращает промис, и функцией, которая была объявлена с помощью ключевого слова
Взгляните на следующий фрагмент кода:
Как видите, обе функции имеют одно и то же тело, в котором мы пытаемся получить доступ к свойству аргумента, который не определен в обоих случаях. Единственное различие между этими двумя функциями заключается в том, что
Это значит, что JavaScript гарантирует, что функция
Однако в случае с функцией
Я знаю, о чем вы сейчас думаете:
Угадал?
Ну, давайте создадим простое приложение, которое делает именно это.
Допустим, у нас есть приложение, созданное с помощью Express и MongoDB, использующее драйвер MongoDB Node.JS. Если вы мне не доверяете, я разместил весь исходный код в этом репозитории Github, поэтому вы можете клонировать его и запустить локально, но я также продублирую весь код здесь.
Вот наш файл
Внимательно посмотрите на блок
Файл
И, наконец, у нас есть файл
Если вы снова посмотрите на файл
Допустим, вы переходите по следующему адресу:
Ну, если вы ответили: «Я увижу огромную ошибку от MongoClient» — вы были правы. Чтобы быть точнее, вы увидите следующую ошибку:
И как вы думаете, будет ли вызван блок
Нет. Он не будет вызван.
А что произойдет, если вы измените объявление функции на это?
Ага, вы начинаете понимать, что к чему. Наш блок
Я надеюсь, что для некоторых из вас эта информация оказалась полезной. Обратите внимание, что этой статьей я не пытаюсь заставить вас всегда использовать асинхронные функции — хотя они довольно крутые. У них есть свои варианты использования, но они по-прежнему являются синтаксическим сахаром над промисами.
Я просто хотел, чтобы вы знали, что иногда промисы могут иметь большое значение, и когда (да, не «если») вы столкнетесь с ошибкой, рассмотренной в этой статье, вы будете знать возможную причину ее появления.
P.S. Прим. перев.: к оригинальной статье был оставлен полезный комментарий от пользователя Craig P Hicks, который (после замечаний в комментариях) я решил привести тут:
Таким образом, чтобы пример из начала статьи «ловил» ошибки в обоих случаях, нужно возвращать в функциях не
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(), ошибка не будет поймана и вывалится в консоль.Более жизненный пример
Я знаю, о чем вы сейчас думаете:
«Когда же, черт возьми, я совершу такую ошибку?»
Угадал?
Ну, давайте создадим простое приложение, которое делает именно это.
Допустим, у нас есть приложение, созданное с помощью Express и MongoDB, использующее драйвер MongoDB Node.JS. Если вы мне не доверяете, я разместил весь исходный код в этом репозитории 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) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <=== ВОТ ЭТОТ! }) 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. Как думаете, что произойдет дальше?Ну, если вы ответили: «Я увижу огромную ошибку от MongoClient» — вы были правы. Чтобы быть точнее, вы увидите следующую ошибку:
Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters.И как вы думаете, будет ли вызван блок
.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() будет вызван, и мы сможем обработать пойманную ошибку и показать ее пользователю.Вместо заключения
Я надеюсь, что для некоторых из вас эта информация оказалась полезной. Обратите внимание, что этой статьей я не пытаюсь заставить вас всегда использовать асинхронные функции — хотя они довольно крутые. У них есть свои варианты использования, но они по-прежнему являются синтаксическим сахаром над промисами.
Я просто хотел, чтобы вы знали, что иногда промисы могут иметь большое значение, и когда (да, не «если») вы столкнетесь с ошибкой, рассмотренной в этой статье, вы будете знать возможную причину ее появления.
P.S. Прим. перев.: к оригинальной статье был оставлен полезный комментарий от пользователя Craig P Hicks, который (после замечаний в комментариях) я решил привести тут:
Хотел бы обратить внимание на одну деталь, (в моей среде разработки) ошибки, которые происходят в телеPromise.resolve({<body>})не «ловятся»:
Promise.resolve((()=>{throw "oops"; })()) .catch(e=>console("Catched ",e)); // блок .catch() не срабатывает и ошибка не "ловится"
но ошибки, возникающие в телеnew Promise()(прим. перев.: в оригинале «proper Promise»), «ловятся»:
(new Promise((resolve,reject)=>{ resolve((()=>{throw "oops"})()) })) .catch(e=>console.log("Catched ",e)); // Catched oops
Как насчет этого утверждения:
async function fn() { <body> }
семантически такой вариант эквивалентен этому:
function fn() { return new Promise((resolve,reject)=>{ resolve({ <body> }) }) }
Следовательно, фрагмент кода ниже будет отлавливать ошибки, если в <body> будетnew Promise()(прим. перев.: в оригинале «proper Promise»):
function fn() { return Promise.resolve({<body}); }
Таким образом, чтобы пример из начала статьи «ловил» ошибки в обоих случаях, нужно возвращать в функциях не
Promise.resolve(), а new Promise():function fn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } async function asyncFn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } asyncFn().catch(err => console.error("Catched")); // => 'Catched' fn().catch(err => console.error("Catched")); // => 'Catched'
