125

Async/Await是这样简化JavaScript代码的 | Fundebug博客

 6 years ago
source link: https://blog.fundebug.com/2017/10/16/async-await-simplify-javascript/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了50亿+错误事件,付费客户有阳光保险、达令家、核桃编程、荔枝FM、微脉等众多品牌企业。欢迎大家免费试用

wechat_slogan.png

您的用户遇到BUG了吗?

体验Demo 免费使用

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK