[译]使用JavaScript Promise的优势

本文译自@Mohsan Riaz发表在medium的文章,已获其受权,原文地址见传送门javascript

在Javascript中编写异步代码经常使人不安,尤为是在使用continuation-passing style(CPS)风格的时候。异步代码影响代码的可读性,使代码很难解耦为更小的独立代码块,致使其更易出错,且之后很难改动。java

译者注:continuation-passing style,简单来讲就是函数的返回不依靠return语句,而是依靠额外传入一个回调函数,将返回值做为回调函数的参数来传出。具体可参考这篇博客编程

若是有一种能够以相似同步代码的风格来写异步代码的方式,那咱们的编程生涯确定会更加的愉快。使用JavaScript的promise能够作到这一点。promise

让咱们先开始尝试找找使用callback有什么问题。markdown

Callbacks

例如:若是须要获取一个国家的列表,紧接着获取国家列表中第一个国家的的城市列表,而后紧接着获取这个城市列表中的全部大学,最终展现第一个城市的第一所大学。异步

因为每次调用都相互依赖于上一次调用的结果,须要调用一系列的嵌套的回调函数。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

为何用Promise

在我看来,与简单的回调处理(continuation-passing style)相比,promise有4个主要优势:fetch

  1. 更好的定义异步逻辑控制流
  2. 解耦
  3. 更好的错误处理
  4. 提高可读性

Promise的使用

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 vs Event listener

若是一个事件但愿能被屡次触发,事件监听器就很是适用,例如按钮点击事件。Promise和事件监听器很类似,但他们有两点本质的不一样:

  1. 不管是异常仍是正常值Promise都只会决议一次。
  2. 即便在Promise决议后添加的回调函数任然会被调用,这意味着能够稍后检查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很是有用,尤为是成功或者失败回调函数只能被执行一次。但在一些实践中,尤为是事件回调函数会被屡次调用的实践中,普通的回调函数更好。感谢你阅读这篇文章,若是有什么问题,请在评论区留言。

初次翻译,若有问题欢迎指正。

相关文章
相关标签/搜索