forked from Automattic/mongoose
-
Notifications
You must be signed in to change notification settings - Fork 1
/
async-await.pug
172 lines (133 loc) · 6.75 KB
/
async-await.pug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
extends layout
append style
link(rel="stylesheet", href="/docs/css/inlinecpc.css")
script(type="text/javascript" src="/docs/js/native.js")
block content
<a class="edit-docs-link" href="#{editLink}" target="_blank">
<img src="/docs/images/pencil.svg" />
</a>
:markdown
## Async/Await
<script>
_native.init("CK7DT53U",{
targetClass: 'native-inline'
});
</script>
<div class="native-inline">
<a href="#native_link#"><span class="sponsor">Sponsor</span> #native_company# — #native_desc#</a>
</div>
ul
li
a(href="#basic-use") Basic Use
li
a(href="#async-functions") Async Functions
li
a(href="#queries") Queries
h3(id="basic-use") Basic Use
:markdown
Async/await lets us write asynchronous code as if it were synchronous. This is especially helpful for avoiding callback hell when executing multiple async operations in sequence--a common scenario when working with Mongoose. Each of the three functions below retrieves a record from the database, updates it,
and prints the updated record to the console.
```javascript
// Works.
function callbackUpdate() {
MyModel.findOne({firstName: 'franklin', lastName: 'roosevelt'}, function(err, doc) {
if(err) {
handleError(err);
}
doc.middleName = 'delano';
doc.save(function(err, updatedDoc) {
if(err) {
handleError(err);
}
// Final logic is 2 callbacks deep
console.log(updatedDoc);
});
})
}
// Better.
function thenUpdate() {
MyModel.findOne({firstName: 'franklin', lastName: 'roosevelt'})
.then(function(doc) {
doc.middleName = 'delano';
return doc.save();
})
.then(console.log)
.catch(function(err) {
handleError(err);
});
};
// Best?
async function awaitUpdate() {
try {
const doc = await MyModel.findOne({
firstName: 'franklin',
lastName: 'roosevelt'
});
doc.middleName = 'delano';
console.log(await doc.save());
}
catch(err) {
handleError(err);
}
}
```
:markdown
Note that the specific fulfillment values of different Mongoose methods vary, and may be affected by configuration. Please refer to the [API documentation](./api.html) for information about specific methods.
h3(id="async-functions") Async Functions
:markdown
Adding the keyword *async* to a JavaScript function automatically causes it to return a native JavaScript promise. This is true [regardless of the return value we specify in the function body](http://thecodebarbarian.com/async-functions-in-javascript.html#an-async-function-always-returns-a-promise).
```javascript
async function getUser() {
//Inside getUser, we can await an async operation and interact with
//foundUser as a normal, non-promise value...
const foundUser = await User.findOne({name: 'bill'});
console.log(foundUser); //Prints '{name: 'bill', admin: false}'
return foundUser;
}
//However, because async functions always return a promise,
//user is a promise.
const user = getUser();
console.log(user) //Oops. Prints '[Promise]'
```
:markdown
Instead, treat the return value of an async function as you would any other promise. Await its fulfillment inside another async function, or chain onto it using ‘.then’ blocks.
```javascript
async function getUser() {
const foundUser = await User.findOne({name: 'bill'});
return foundUser;
};
async function doStuffWithUser() {
//Await the promise returned from calling getUser.
const user = await getUser();
console.log(user); //Prints '{name: 'bill', admin: false}'
}
h3(id="queries") Async/Await with Mongoose Queries
:markdown
Under the hood, [*async/await* is syntactic sugar](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await)
over the Promise API. Due to the surprisingly simple way promises are implemented in JavaScript, the keyword *await* will try to unwrap any object with a property whose key is the string ‘then’ and whose value is a function. Such objects belong to a broader class of objects called thenables. If the thenable being unwrapped is a genuine promise, e.g. an instance of the [Promise constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), we enjoy several guarantees about how the object’s ‘then’ function will behave. However, Mongoose provides several static helper methods that return a different class of thenable object called a [Query](./queries.html)--and [Queries are not promises](./queries.html#queries-are-not-promises). Because Queries are also *thenables*, we can interact with a Query using async/await just as we would interact with a genuine promise, with one key difference: observing the fulfillment value of a genuine promise cannot under any circumstances change that value, but trying to re-observe the value of a Query may cause the Query to be re-executed.
```javascript
function isPromise(thenable) {
return thenable instanceof Promise;
};
// The fulfillment value of the promise returned by user.save() will always be the same,
// regardless of how, or how often, we observe it.
async function observePromise() {
const user = await User.findOne({firstName: 'franklin', lastName:'roosevelt'});
user.middleName = 'delano';
// Document.prototype.save() returns a *genuine* promise
const realPromise = user.save();
console.log(isPromise(realPromise)); //true
const awaitedValue = await realPromise;
realPromise.then(chainedValue => console.log(chainedValue === awaitedValue)); //true
}
// By contrast, the value we receive when we try to observe the same Query more than
// once is different every time. The Query is re-executing.
async function observeQuery() {
const query = User.findOne({firstname: 'leroy', lastName: 'jenkins'});
console.log(isPromise(query)); //false
const awaitedValue = await query;
query.then(chainedValue => console.log(chainedValue === awaitedValue)); //false
}
```
:markdown
You are most likely to accidentally re-execute queries in this way when mixing callbacks with async/await. This is never necessary and should be avoided. If you need a Query to return a fully-fleged promise instead of a thenable, you can use [Query#exec()](./api/query.html#query_Query-exec).