异步函数和返回承诺的函数之间的区别

简单地返回promise的函数和使用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声明了asyncFn

这意味着JavaScript可以确保asnycFn函数返回一个promise(成功或失败),即使其中发生错误,在我们的例子中, .catch()块也可以捕获它。

但是,对于fn函数,引擎尚不知道该函数将返回.catch() ,因此代码将不会到达.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查看app.js文件,您将看到当我们进入localhost:3000/users/<id>我们将调用user-model.js定义的getUserById函数,并将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()块将被调用,我们将能够处理捕获的错误并将其显示给用户。

而不是结论


希望这些信息对某些人有所帮助。 请注意,本文并不试图强迫您始终使用异步功能-尽管它们非常酷。 它们有自己的用例,但它们仍然是诺言之上的语法糖。

我只是想让您知道有时许诺会产生很大的变化,并且当您(是,不是“如果”)遇到本文中讨论的错误时,您将知道其发生的可能原因。

PS注 perev。:原始文章为用户Craig P Hicks留下了有用的评论,我(在评论之后)决定在此处引用:
我想提请注意一个细节,在我的开发环境中, Promise.resolve({<body>})主体中发生的错误未被“发现”:

 Promise.resolve((()=>{throw "oops"; })()) .catch(e=>console("Catched ",e)); //  .catch()      "" 

但是在new Promise()正文new Promise() 原始“正确的Promise”中大约为transl .:)的主体中发生的错误被“捕获”:

 (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”中为大约Transl。:) ,则下面的代码片段将捕获错误:

 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' 

Source: https://habr.com/ru/post/zh-CN475260/


All Articles