Async/Await是这样简化JavaScript代码的

译者按:Async/Await 替代 Promise 的 6 个理由中,我们比较了两种不同的异步编程方法:Async/AwaitPromise,这篇博客将通过示例代码介绍Async/Await是如何简化 JavaScript 代码的。

本文采用意译,版权归原作者所有

Async/Await是 JavaScript 的ES7新特性,来源于.NETC#。它可以不用回调函数,像同步代码那些编写异步代码。这篇博客将通过一些代码示例,来说明Async/Await如何简化 JavaScript 代码。

1. 去除回调函数

运行本文的示例代码,并不需要额外的函数库。对于最新版的主流浏览器中,例如 Chrome,Firefox, Safari 以及 Edge,它们都支持 Async/Await 语法。另外,Node.js 7.6+也支持了 Async/Await 语法。

我们编写了一些简单的 API 接口,用于模拟异步操作。这些接口都返回 Promise,并在 200ms 后resolve一些数据。

class Api {
constructor() {
this.user = { id: 1, name: "test" };
this.friends = [this.user, this.user, this.user];
this.photo = "not a real photo";
}

getUser() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.user), 200);
});
}

getFriends(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.friends.slice()), 200);
});
}

getPhoto(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.photo), 200);
});
}

throwError() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Intentional Error")), 200);
});
}
}

嵌套 Promise

function callbackHell() {
const api = new Api();
let user, friends;
api.getUser().then(function(returnedUser) {
user = returnedUser;
api.getFriends(user.id).then(function(returnedFriends) {
friends = returnedFriends;
api.getPhoto(user.id).then(function(photo) {
console.log("callbackHell", { user, friends, photo });
});
});
});
}

曾经使用 Promise 编写回调函数的开发者一定不会陌生,这样一层层的嵌套代码通常是这样结尾的:

      })
})
})
}

在回调函数中调用回调函数,一层层地嵌套,这就是所谓的“回调地狱”。在真实的代码中,这样的情况并不少见,通常更为复杂。

链式 Promise

function promiseChain() {
const api = new Api();
let user, friends;
api.getUser()
.then(returnedUser => {
user = returnedUser;
return api.getFriends(user.id);
})
.then(returnedFriends => {
friends = returnedFriends;
return api.getPhoto(user.id);
})
.then(photo => {
console.log("promiseChain", { user, friends, photo });
});
}

Promise 的最佳特性之一,就是可以在then回调函数中,return 一个新的 Promise,这样就可以将这些 Promise 链接起来,只有一层嵌套。链式 Promise嵌套 Promise简单很多,但是还是很多冗余。

Async/Await

不使用回调函数可以吗?当然可以!使用Async/Await的话,7 行代码就可以搞定。

async function asyncAwaitIsYourNewBestFriend() {
const api = new Api();
const user = await api.getUser();
const friends = await api.getFriends(user.id);
const photo = await api.getPhoto(user.id);
console.log("asyncAwaitIsYourNewBestFriend", { user, friends, photo });
}

使用await关键词时,赋值操作将等到异步操作结束时才进行。这样,看起来与同步代码无异,实际执行事实上是异步的。

2. 简化循环

Async/Await可以让一些复杂操作,比如循环变得简单。例如,当我们需要获取某个 user 的所有 friends 的 friends 列表,应该怎样操作呢?

使用 Promise

function promiseLoops() {
const api = new Api();
api.getUser()
.then(user => {
return api.getFriends(user.id);
})
.then(returnedFriends => {
const getFriendsOfFriends = friends => {
if (friends.length > 0) {
let friend = friends.pop();
return api.getFriends(friend.id).then(moreFriends => {
console.log("promiseLoops", moreFriends);
return getFriendsOfFriends(friends);
});
}
};
return getFriendsOfFriends(returnedFriends);
});
}

我们使用了递归函数getFriendsOfFriends来获取friends-of-friends,知道friends数组为空。如此简单的任务,这样写显然过于复杂了。

使用Promise.all()来实现的话,则并非循环,而是并发执行。

使用 Async/Await

async function asyncAwaitLoops() {
const api = new Api();
const user = await api.getUser();
const friends = await api.getFriends(user.id);

for (let friend of friends) {
let moreFriends = await api.getFriends(friend.id);
console.log("asyncAwaitLoops", moreFriends);
}
}

这时,可以直接使用 for 循环来实现,非常简单。

3. 简化并发

使用循环逐个获取friends-of-friends显然太慢,采用并发方式更为简单。

async function asyncAwaitLoopsParallel() {
const api = new Api();
const user = await api.getUser();
const friends = await api.getFriends(user.id);
const friendPromises = friends.map(friend => api.getFriends(friend.id));
const moreFriends = await Promise.all(friendPromises);
console.log("asyncAwaitLoopsParallel", moreFriends);
}

为了实现并发,只需要将 Promise 数组作为Promise.all()的参数即可。这样,只需要await一个 Promise,而这个 Promise 会在所有并发操作结束时resolve

4. 简化错误处理

使用回调函数处理 Promise 错误

function callbackErrorHell() {
const api = new Api();
let user, friends;
api.getUser().then(
function(returnedUser) {
user = returnedUser;
api.getFriends(user.id).then(
function(returnedFriends) {
friends = returnedFriends;
api.throwError().then(
function() {
console.log("Error was not thrown");
api.getPhoto(user.id).then(
function(photo) {
console.log("callbackErrorHell", {
user,
friends,
photo
});
},
function(err) {
console.error(err);
}
);
},
function(err) {
console.error(err);
}
);
},
function(err) {
console.error(err);
}
);
},
function(err) {
console.error(err);
}
);
}

这样做非常糟糕,代码非常冗余,可读性也很差。

使用 catch 方法处理 Promise 错误

function callbackErrorPromiseChain() {
const api = new Api();
let user, friends;
api.getUser()
.then(returnedUser => {
user = returnedUser;
return api.getFriends(user.id);
})
.then(returnedFriends => {
friends = returnedFriends;
return api.throwError();
})
.then(() => {
console.log("Error was not thrown");
return api.getPhoto(user.id);
})
.then(photo => {
console.log("callbackErrorPromiseChain", { user, friends, photo });
})
.catch(err => {
console.error(err);
});
}

这样处理好多了,仅仅需要在 Promise 链的最后,使用 catch 方法处理所有错误。

使用 Try/Catch 处理 Async/Await 错误

async function aysncAwaitTryCatch() {
try {
const api = new Api();
const user = await api.getUser();
const friends = await api.getFriends(user.id);

await api.throwError();
console.log("Error was not thrown");

const photo = await api.getPhoto(user.id);
console.log("async/await", { user, friends, photo });
} catch (err) {
console.error(err);
}
}

对于Async/Await代码,使用Try/Catch即可处理,和同步代码一样,更加简单。

如何你需要监控线上 JavaScript 代码的错误时,可以免费使用Fundebug的实时错误监控服务,只需要一行代码就可以搞定!

5. 简化代码组织

使用async关键词定义的函数都会返回Promise,这样可以更方便地组织代码。

例如,在之前的示例中,我们可以将获取的 user 信息 return,而不是直接打印;然后,我们可以通过返回的 Promise 来获取 user 信息。

async function getUserInfo() {
const api = new Api();
const user = await api.getUser();
const friends = await api.getFriends(user.id);
const photo = await api.getPhoto(user.id);
return { user, friends, photo };
}

function promiseUserInfo() {
getUserInfo().then(({ user, friends, photo }) => {
console.log("promiseUserInfo", { user, friends, photo });
});
}

使用Async/Await语法,则更加简单:

async function awaitUserInfo() {
const { user, friends, photo } = await getUserInfo();
console.log("awaitUserInfo", { user, friends, photo });
}

如何获取多个 user 的信息?

async function getLotsOfUserData() {
const users = [];
while (users.length < 10) {
users.push(await getUserInfo());
}
console.log("getLotsOfUserData", users);
}

如何并发?如何处理错误?

async function getLotsOfUserDataFaster() {
try {
const userPromises = Array(10).fill(getUserInfo());
const users = await Promise.all(userPromises);
console.log("getLotsOfUserDataFaster", users);
} catch (err) {
console.error(err);
}
}

关于Fundebug

Fundebug专注于JavaScript、微信小程序、支付宝小程序线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了80亿+错误事件。欢迎大家免费试用

版权声明

转载时请注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2017/10/16/async-await-simplify-javascript/

您的用户遇到BUG了吗?

体验Demo 免费使用