JavaScript中的Promises

Posted by cl9000 on April 04, 2020

那些疯狂到认为自己能够改变世界的人,才能真正改变世界。——<史蒂夫·乔布斯>

作者:Ashish Lahoti
译者:cl9000
来源:https://codingnconcepts.com/javascript/promises-in-javascript/

Promises 是在 ES6 中原生引入的。它们与我们的 Promises 非常相似。当我们遵守或违背 Promises 时,JavaScriptPromises 也会是 resolvereject。在这篇文章中,我们将讨论为什么我们应该使用promise, promise语法,promise状态和它的实际用法,以及使用 fetch API的例子。

为什么是 Promises?

在早期,可以使用回调来处理异步操作。然而,回调函数的功能有限,并且经常会导致难以管理的代码。如果你要处理多个异步调用,它会导致大量嵌套的回调代码,这也被称为回调地狱。

引入 Promises 是为了提高代码的可读性和更好地处理异步调用和错误。

Promises 的语法

我们看看简单promise对象的语法。

1
2
3
let promise = new Promise(function (resolve, reject) {
// asynchronous call
});

Promise 接受一个回调函数作为参数,该回调函数接受两个参数——第一个是 resolve 函数,第二个是 reject 函数。 Promise 可以用一个值来 fulfilled (实现),也可以用一个理由(错误)来 rejected(拒绝)。

Promise 状态

Promise 对象有以下三种状态:

  • pending: 初始状态。
  • completed: 表示成功状态。调用 resolve() 方法。
  • reject: 表示失败状态,调用 reject()

使用

我们通常使用 异步调用,使用 fetch APIHTTP 端点获取数据。我们看看这个例子,在这种情况下 Promise 是如何使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//create a promise object
let getUsers = new Promise(function (resolve, reject ){
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});

//call promise object
getUsers
.then((data) => {
console.log(data);
})
.catch(error => {
console.log(error);
});

Promise 链式调用

当必须使用一个异步调用的输出作为另一个异步调用的输入时,Promise 链式调用就会发挥作用。在这种情况下,您可以链接多个 Promise

让我们看看下面的例子,我们首先使用 getUser 异步调用获取用户列表,然后通过传递 userId将它与 getPosts 链接起来。

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
//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});

let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getPosts = (userId) => getData(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);

//chained promises to fetch all posts by first user (userId = 1)
getUsers.then((data) => {
const user = data[0];
return getPosts(user.id);
})
.then((data) => {
console.log(data);
})
.catch(error => {
console.log(error);
});

Output
▼ (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
➤ 0: {userId: 1, id: 1, title: “sunt aut facere repellat provident occaecati excepturi optio reprehenderit”, body: “quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto”}
➤ 1: {userId: 1, id: 2, title: “qui est esse”, body: “est rerum tempore vitae↵sequi sint nihil reprehend…aperiam non debitis possimus qui neque nisi nulla”}
➤ 2: {userId: 1, id: 3, title: “ea molestias quasi exercitationem repellat qui ipsa sit aut”, body: “et iusto sed quo iure↵voluptatem occaecati omnis e…↵molestiae porro eius odio et labore et velit aut”}
➤ 3: {userId: 1, id: 4, title: “eum et est occaecati”, body: “ullam et saepe reiciendis voluptatem adipisci↵sit … ipsam iure↵quis sunt voluptatem rerum illo velit”}
➤ 4: {userId: 1, id: 5, title: “nesciunt quas odio”, body: “repudiandae veniam quaerat sunt sed↵alias aut fugi…sse voluptatibus quis↵est aut tenetur dolor neque”}
➤ 5: {userId: 1, id: 6, title: “dolorem eum magni eos aperiam quia”, body: “ut aspernatur corporis harum nihil quis provident …s↵voluptate dolores velit et doloremque molestiae”}
➤ 6: {userId: 1, id: 7, title: “magnam facilis autem”, body: “dolore placeat quibusdam ea quo vitae↵magni quis e…t excepturi ut quia↵sunt ut sequi eos ea sed quas”}
➤ 7: {userId: 1, id: 8, title: “dolorem dolore est ipsam”, body: “dignissimos aperiam dolorem qui eum↵facilis quibus…↵ipsam ut commodi dolor voluptatum modi aut vitae”}
➤ 8: {userId: 1, id: 9, title: “nesciunt iure omnis dolorem tempora et accusantium”, body: “consectetur animi nesciunt iure dolore↵enim quia a…st aut quod aut provident voluptas autem voluptas”}
➤ 9: {userId: 1, id: 10, title: “optio molestias id quia eum”, body: “quo et expedita modi cum officia vel magni↵dolorib…it↵quos veniam quod sed accusamus veritatis error”}
length: 10
proto: Array(0)

Promise.all()

Promise.all() 在你想要执行多个异步调用并等待所有调用完成并获得集合输出时非常有用。

Promise.all() 接受一个 Promise 数组,并以相同的 Promise 序列返回一个结果数组。如果任何一个异步调用失败,它将抛出异常。

我们看下面的例子,在这个例子中,我们同时获取了三个异步调用 getUsersgetPostsgetComments 的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});

//create multiple promises from common getData function
let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getPosts = getData('https://jsonplaceholder.typicode.com/posts');
let getComments = getData('https://jsonplaceholder.typicode.com/comments');

//fetch data to get users, posts and comments collectively
Promise.all([getUsers, getPosts, getComments]).then(result => {
console.log(result);
});

Output
▼ (3) [Array(10), Array(100), Array(500)]
➤ 0: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
➤ 1: (100) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
➤ 2: (500) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
length: 3
proto: Array(0)

Promise.allSettled()

Promise.allsettle() 类似于 Promise.all() 来执行多个异步调用。这两者唯一的区别,

  • Promise.all() 要么是all resolve,要么是 any reject ,这意味着如果任何异步调用失败,它将返回一个错误。
  • Promise.allsettle() 都是已解决的,意味着如果任何异步调用失败,它不会返回错误。它给出所有成功和失败的异步调用的集合输出。
    我们看看下面的例子,其中 getPostsFails 异步调用返回 404 错误,因为 url 端点不存在,但您仍然能够获取数据的 getUsersgetComments 异步调用。
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
//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
fetch(url)
.then(response => {
if(response.ok){
return response.json();
}else{
throw `Error ${response.status}`;
}
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});

//create multiple promises from common getData function
let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getPostsFails = getData('https://jsonplaceholder.typicode.com/postsfailes');
let getComments = getData('https://jsonplaceholder.typicode.com/comments');

//fetch data to get users, posts and comments collectively regardless of any error
Promise.allSettled([getUsers, getPostsFails, getComments]).then(result => {
console.log(result);
});

Output
▼ (3) [{…}, {…}, {…}]
➤ 0: {status: “fulfilled”, value: Array(10)}
➤ 1: {status: “rejected”, reason: “Error 404”}
➤ 2: {status: “fulfilled”, value: Array(500)}
length: 3
proto: Array(0)

Promise.race()

Promise.race() 在你想从多个异步调用中获取任何一个异步调用的数据时非常有用。

看下面的例子,我们感兴趣的是来自 getTodosgetUsersgetComments 的任何数据,哪个先解析。在我们的例子中,首先解析 getUsers 并返回用户列表。

请注意,如果您反复执行相同的代码片段,结果可能会根据网络连接和首先解析的是哪一个而不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});

//create multiple promises from common getData function
let getTodos = getData('https://jsonplaceholder.typicode.com/todos');
let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getComments = getData('https://jsonplaceholder.typicode.com/comments');


//fetch either todos or users or comments whichever resolves first
Promise.race([getTodos, getUsers, getComments]).then(result => {
console.log(result);
});

Output
▼ (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
➤ 0: {id: 1, name: “Leanne Graham”, username: “Bret”, email: "Sincere@april.biz", address: {…}, …}
➤ 1: {id: 2, name: “Ervin Howell”, username: “Antonette”, email: "Shanna@melissa.tv", address: {…}, …}
➤ 2: {id: 3, name: “Clementine Bauch”, username: “Samantha”, email: "Nathan@yesenia.net", address: {…}, …}
➤ 3: {id: 4, name: “Patricia Lebsack”, username: “Karianne”, email: "Julianne.OConner@kory.org", address: {…}, …}
➤ 4: {id: 5, name: “Chelsey Dietrich”, username: “Kamren”, email: "Lucio_Hettinger@annie.ca", address: {…}, …}
➤ 5: {id: 6, name: “Mrs. Dennis Schulist”, username: “Leopoldo_Corkery”, email: "Karley_Dach@jasper.info", address: {…}, …}
➤ 6: {id: 7, name: “Kurtis Weissnat”, username: “Elwyn.Skiles”, email: "Telly.Hoeger@billy.biz", address: {…}, …}
➤ 7: {id: 8, name: “Nicholas Runolfsdottir V”, username: “Maxime_Nienow”, email: "Sherwood@rosamond.me", address: {…}, …}
➤ 8: {id: 9, name: “Glenna Reichert”, username: “Delphine”, email: "Chaim_McDermott@dana.io", address: {…}, …}
➤ 9: {id: 10, name: “Clementina DuBuque”, username: “Moriah.Stanton”, email: "Rey.Padberg@karina.biz", address: {…}, …}
length: 10
proto: Array(0)

总结 Summary

最好使用Promises而不是回调函数,因为Promises在以下方面可以提供很多帮助:

  • 解决成功的Anyc调用响应 使用 Promise.resolve(response)
  • 拒绝基于状态或数据的异步调用响应 使用 Promise.reject(response)
  • 更好的错误处理 使用 Promise.catch(onRejection)
  • 多个异步调用的链 使用Promise.then(onFulfillment, onRejection)
  • 获取多个异步调用的集合结果 使用Promise.all([promise1, promise2, ...])
  • 获取多个异步调用的集合结果,而不考虑任何错误 使用 Promise.allSettled([promise1, promise2, ...])
  • 从多个异步调用中,得到最先解析的结果 使用Promise.race([promise1, promise2, ...])

参考

关注【公众号】,了解更多。
关注【公众号】,了解更多。
关注【公众号】,了解更多。



支付宝打赏 微信打赏

赞赏一下 坚持原创技术分享,您的支持将鼓励我继续创作!