本文译自@Mohsan Riaz发表在medium的文章,已获其受权,原文地址见传送门javascript
在Javascript中编写异步代码经常使人不安,尤为是在使用continuation-passing style(CPS)风格的时候。异步代码影响代码的可读性,使代码很难解耦为更小的独立代码块,致使其更易出错,且之后很难改动。java
译者注:continuation-passing style,简单来讲就是函数的返回不依靠return语句,而是依靠额外传入一个回调函数,将返回值做为回调函数的参数来传出。具体可参考这篇博客编程
若是有一种能够以相似同步代码的风格来写异步代码的方式,那咱们的编程生涯确定会更加的愉快。使用JavaScript的promise能够作到这一点。promise
让咱们先开始尝试找找使用callback有什么问题。markdown
例如:若是须要获取一个国家的列表,紧接着获取国家列表中第一个国家的的城市列表,而后紧接着获取这个城市列表中的全部大学,最终展现第一个城市的第一所大学。异步
因为每次调用都相互依赖于上一次调用的结果,须要调用一系列的嵌套的回调函数。ide
function fetchCountries(){
fetchJSON("/countries", (success, countries) => {
if(success){
try{
// do some stuff
fetchCities(countries[0].id);
} catch(e){
processError();
}
}else
processError();
});
}
function fetchCities(countryId){
fetchJSON(`countries/${countryId}/cities`, (success, cities) => {
if(success){
try{
// do some stuff
fetchUniversities(cities[0].id);
} catch(e){
processError()
}
}else
processError();
});
}
function fetchUniversities(cityId){
fetchJSON(`cities/${cityId}/universities`, (success, universities) => {
if(success){
try{
// do some stuff
fetchUniversityDetails(universities[0].id);
}catch(e){
processError();
}
}else
processError();
});
}
function fetchUniversityDetails(univId){
fetchJSON(`/universities/${univId}`, (success, university) => {
if(success){
try{
// do some stuff
hideLoader();
}catch(e){
processError();
}
}else
processError();
});
}
复制代码
上面这个实现有什么问题?函数
回调函数没有将异步逻辑放在同一个位置,而是决策接下来调用哪一个函数。简而言之,咱们将控制流给了各个函数,让他们紧密的耦合到了一块儿。oop
在我看来,与简单的回调处理(continuation-passing style)相比,promise有4个主要优势:fetch
Javascript的Promise让异步代码像同步代码同样return一个值,这个返回值是个能承诺成功或失败值的object。这个小改动让它用起来很是强大。
get("/countries")
.then(populateContDropdown)
.then(countries => get(`countries/${countries[0].id}/cities`))
.then(populateCitiesDropdown)
.then(cities => get(`cities/${cities[0].id}/universities`))
.then(populateUnivDropdown)
.then(universities => get(`universities/${universities[0].id}`))
.then(populateUnivDetails)
.catch(showError)
.then(hideLoader)
复制代码
与同步编程类似,一个函数的输出是下一个函数的输入,在调用链中像使用JSON.parse
同样使用JS函数,他们的返回值会被喂给下一个回调函数。
相似JavaScript的try/catch
,若是有异常(exception),catch
函数会将其捕获,后面的代码会照常运行,上面例子中的loader会在执行后隐藏。
咱们在同一个地方定义全部异步逻辑,没必要作额外的检查或者为错误处理使用try/catch
。最终,代码具备更高的可读性,更低的耦合度,可复用的独立函数。
不管什么时候出现有意或无心的异常,都会在下一个catch处理程序中抛出异常。你能够将catch处理程序放置在链中的任何位置以捕获特定的异常,此时能够显示一个错误或者从新取值。
get("/countries")
.then(JSON.parse)
.catch(e => console.log("Could not parse string"))
.then(...)
.catch(e => console.log("Error occured"))
复制代码
在上面的例子中,第二个console
语句将不会被打印。
若是一个异常已被捕获且想将其传到下一个
catch
处理函数中,必须将其抛出。
若是想展现多个错误信息这样就很方便,例如:
get("/coutries")
.then(...)
.catch(e => {
showLowLevelError();
throw e;
})
.catch(showHighLevelError)
复制代码
若是一个事件但愿能被屡次触发,事件监听器就很是适用,例如按钮点击事件。Promise和事件监听器很类似,但他们有两点本质的不一样:
fullfiled
仍是rejected
。这颇有用,由于咱们对发生的结果作出反应更感兴趣。举个图片预加载的简单例子:
var imagePromise = preloadImage("src.png");
setTimeout(() => {
imagePromise.then(() => console.log("image was loaded") )
.catch(()=> console.log("could not load the image"))
}, 2000)
function preloadImage (path) {
return new Promise((resolve, reject) => {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
复制代码
Promise.all([...])
对于批量决议颇有用
Promise.all([imagePromise1, imagePromise2, ....])
.then(...)
.catch(...)
复制代码
在JavaScript中的大部分实践中,Promise很是有用,尤为是成功或者失败回调函数只能被执行一次。但在一些实践中,尤为是事件回调函数会被屡次调用的实践中,普通的回调函数更好。感谢你阅读这篇文章,若是有什么问题,请在评论区留言。
初次翻译,若有问题欢迎指正。