之前阅读过一篇文章《Async/Await替代Promise的6个理由》,现在async / await语法已经处于Stage3阶段。
- 服务端方面,在
Node.js 7.6 版本后,async / await 语法已经被Node.js 支持,如Koa2 已经抛弃generator/yield语法,拥抱async / await 语法。
- 客户端方面,也可以通过
Babel 让我们随心所欲地使用最新的Ecmascript 语法。
从业务说起
单从我个人来讲,是不太喜欢使用这样的最新语法的。一直以来写代码还是拥抱Promise 为主。但自从用上了async / await 语法后好像就会对这种语法产生依赖感(看起来就像是同步阻塞的代码一样,还不用像generator 语法那样手动释放,或使用co 那样的流程控制库)
看下面这些常见的业务场景:
- 假设我们在编写一个刷题的程序,这个程序是智能的(对,它懂你),你每次做完一道题并提交,服务端会根据你提交的结果来判断你有没有掌握当前知识点,如果没有掌握,服务端会再次给你推送下一道题。
- 假设我们在编写一个用户手动对图像进行鉴别的软件,服务端给了两个接口,接口submit用于提交上一张图像的鉴别结果,只有当接口submit提交成功,才能去请求接口next,用于请求下一张图片的链接。
const requestSubmit = () => {
return new Promise((resolve, reject) => {
$.ajax({
url: '/submit',
success: (data) => {
resolve(data);
},
error: (err) => {
reject(err);
},
});
});
};
const requestNext = () => {
return new Promise((resolve, reject) => {
$.ajax({
url: '/next',
success: (data) => {
resolve(data);
},
error: (err) => {
reject(err);
},
});
});
};
我们如果使用Promise 语法:
requestSubmit()
.then((data) => {
// dosomething
return requestNext();
})
.then((data) => {
// dosomething
})
.catch((err) => {
console.log(err);
});
虽然使用Promise ,catch 只能捕获到最近一次Promise.then 的错误,但是实际上现在,其实可以这么捕获每次的错误!
不要忘记Promise.then 的第二个参数:
requestSubmit()
.then((data) => {
// dosomething
return requestNext();
}, (err) => {
console.log(err);
})
.then((data) => {
// dosomething
},(err) => {
console.log(err);
});
现在有一个问题,服务端可能会校验用户登录态,或参数是否正确等情况。如果用户登录态失效,会在第一个ajax 请求中,仍会走success 回调,并不会抛出错误,此时还是得去手动修改requestSubmit 函数。
此外,Promise.catch 方法只会捕获最近一次错误,那这个错误究竟在哪里抛出,其实并不得而知。
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
但是注意:原文其实说得不全,我们这么捕获Promise 的错误是可以的:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise(), err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
.then(() => callAPromise(), err => {
console.log(err);
})
.then(() => callAPromise(), err => {
console.log(err);
})
.then(() => callAPromise(), err => {
console.log(err);
})
.then(() => {
throw new Error("oops");
}, err => {
console.log(err);
});
}
makeRequest();
如果把上述业务场景改为async / await 语法:
(async function() {
try {
const res1 = await axios.post('/submit');
// dosomething
} catch (err) {
console.log(err);
} finally {
console.log('go next!');
}
try {
const res2 = await axios.get('/next');
// dosomething
} catch (err) {
console.log(err);
} finally {
console.log('done!');
}
})();
无论从异常捕获方面还是从代码可读性方面,都会感觉更胜一筹。
这里其实大多数就是捡取那篇文章:
使用async / await 明显节约了不少代码。我们不需要写.then ,不需要写匿名函数处理Promise 的resolve 值,也不需要定义多余的data变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显。
async / await 让try / catch 可以同时处理同步和异步错误。在下面的Promise 示例中,try / catch 不能处理JSON.parse 的错误,因为它在Promise 中。我们需要使用.catch ,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。
const makeRequest = () => {
try {
getJSON()
.then(result => {
// JSON.parse可能会出错
const data = JSON.parse(result)
console.log(data)
})
// 取消注释,处理异步代码的错误
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}
使用async / await 的话,catch 能处理JSON.parse 错误:
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
这一点在上边也说了:
下面示例中,需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
这些代码看着就头痛。嵌套(6层),括号,return 语句很容易让人感到迷茫,而它们只是需要将最终结果传递到最外层的Promise 。
上面的代码使用async / await 编写可以大大地提高可读性:
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
你很可能遇到过这样的场景,调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。你的代码很可能是这样的:
const makeRequest = () => {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
如果promise3 不需要value1 ,可以很简单地将promise 嵌套铺平。如果你忍受不了嵌套,你可以将value 1 & 2 放进Promise.all 来避免深层嵌套:
const makeRequest = () => {
return promise1()
.then(value1 => {
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
return promise3(value1, value2)
})
}
这里可见上边真实的业务例子。
async/await 能够使得代码调试更简单。2个理由使得调试Promise 变得非常痛苦:
- 如果你在
.then 代码块中设置断点,使用Step Over 快捷键,调试器不会跳到下一个.then ,因为它只会跳过异步代码。使用await / async 时,你不再需要那么多箭头函数,这样你就可以像调试同步代码一样跳过await 语句。
|