这是对Flattened Promise Chains的翻译,水平有限请见谅^ ^。

Promises对于解决复杂异步请求与响应问题堪称伟大。AngularJS提供了$q和$http来实现它;还有很多类似技术这里不做展开。

Promises允许开发者很容易得将请求通知与异步请求进行绑定,而它还有另外两个重要的忒性:

  • 在后续请求处理函数被通知前对传参进行转换
  • 在响应中可以触发更多的promise式的异步请求

但是比以上更重要的是,Promises支持自定义链式活动或计算,管理异步调用链是一个非常困难和复杂的行为,Promise令人惊奇的做到了这一点。

但是这依然隐藏了一些反模式,后面我将对其进行讨论。

The FlightDashboard

看下Travel Service,它载入即将出发的用户信息,下面的代码一个远程服务返回了一个json格式响应数据,另外请注意这是一个promise式的异步请求。

var TravelService = function($http) {
return {
getDeparture: function(user) {
return $http.get(
URL_LAST_FLIGHT,
{userID : user.email}
)
}
}
}

现在让我使用该服务区载入用户的预定航班:

var FlightDashboard = function($scope, user, travelService) {

travelService

.getDeparture(user)

.then(function(departure) {

// Publish the departure details to the view

$scope.departure = departure

})

$scope.departure = null

}

非常棒,这里没有什么让我们感到陌生的新知识,下面我们再来些更加贴近实际的复杂场景。

嵌套Promise调用链

现在我们假设,当我们收到航班信息,就查看天气预报和航班状态,这个场景有三个序列的级联:getDeparture() -> getFlight() -> getForecast()

var FlightDashboard = function($scope, user, travelService, weatherService) {
// level 1
travelService
.getDeparture(user.email)
.then(function(departure) {
$scope.departure = departure // level2
travelService
.getFlight(departure.flightId)
.then(function(flight) {
$scope.flight = flight
// level 3
weatherService
.getForecast(departure.date)
.then(function(weather) {
$scope.weather = weather
})
})
})
}

以上代码展示了我们不断在成功回调函数调用下一级请求。

这里出现了一个深层嵌套的序列化的、级联的三级调用链。请求载入用户即将搭乘的航班、航班信息和天气情况。

注意这里没有考虑错误处理,任意的嵌套rejection可能并不会像你想的那样传播下去。

扁平化promises调用链

如果每级逻辑都很复杂,那么这种深层嵌套是难以管理的,而且开发者还要细心考虑每级调用链的错误处理。
我个人认为这种嵌套是**anti-pattern**的,这里我从错误处理、代码简洁和可维护性角度权衡后对对上述代码进行了重构,实际一个promise回调可以返回以下内容:
  • 一个值 - 我们可以将它向下分发并通过resolve回调处理。
  • 一个promise - 它将创建一个异步分支。
  • 一个异常 - rejection后续promise活动。
  • 一个rejected promise - 向下分发病通过reject回调处理。

由于promise回调可以返回promises,让我们重构上述代码。1218

var FlightDashboard = function ($scope, user, flightService, weatherService) {
travelService
.getDeparture(user)
.then(function (departure) {
$scope.departure = departure
return travelService.getFlight(departure.flightId)
})
.then(function (flight) {
$scope.flight = flight
return weatherService.getForecast($scope.departure.date)
})
.then(function (weather) {
$scope.weather = weather
})
$scope.flight = null;
$scope.planStatus = null;
$scope.forecast = null;
}

这里最重要的改变就是每个response回调都返回的是promise。

请记住成功回调中可以返回一个值、跑出一个异常或者返回一个promise

对于之前的深层嵌套这是一个不错的解决方法,但是我并不喜欢在成功回调中去调用另一个promise式API,如果可以将这些冗余的函数剔除那就更好了。

这里有两个很明显的anti-pattern:

  • 我们在每级修改$scope变量改为在每个成功回调中修改,
  • 我们用$scope.departure.date代替以前的直接参数传递。

更好的重构

如果我们自处理request-response呢?
var FlightDashboard = function ($scope, user, flightService, weatherService) {
travelService
.getDeparture(user)
.then(function (departure) {
$scope.departure = departure
return travelService.getFlight(departure.flightId)
})
.then(function (flight) {
$scope.flight = flight
return weatherService.getForecast($scope.departure.date)
})
.then(function (weather) {
$scope.weather = weather
})
$scope.flight = null;
$scope.planStatus = null;
$scope.forecast = null;
} var FlightDashboard = function ($scope, user, travelService, weatherService) {
var loadDeparture = function (user) {
return travelService
.getDeparture(user.email) // Request #1
.then(function (departure) {
$scope.departure = departure; // Response Handler #1
return departure.flightID;
});
},
loadFlight = function (flightID) {
return travelService
.getFlight(flightID) // Request #2
.then(function (flight) {
$scope.flight = flight; // Response Handler #2
return flight;
});
},
loadForecast = function () {
return weatherService
.getForecast($scope.departure.date) // Request #3
.then(function (weather) {
$scope.weather = weather; // Response Handler #3
return weather;
});
};
loadDeparture(user)
.then(loadFlight)
.then(loadForecast) $scope.user = user;
$scope.departure = null;
$scope.flight = null;
$scope.weather = null;
}

可以看到这里仍然存在anti-pattern (2)。

最后

这里我们再考虑下级与级之前的依赖关系,获取航班信息和天气情况其实不需要有层级关系而是平级的,因此最后我们对代码再进行下处理。

var FlightDashboard = function( $scope, user, travelService, weatherService, $q, $log )
{
var loadFlight = function( user )
{
return travelService.getDeparture( user.email ); // Request #1
},
parallelLoad = function ( departure )
{
// Execute #2 & #3 in parallel...
return $q.all([
travelService.getFlight( departure.flightID ), // Request #2
weatherService.getForecast( departure.date ) // Request #3
])
.then( $q.spread( function( flight, weather )
{
$scope.departure = departure; // Response Handler #1
$scope.flight = flight; // Response Handler #2
$scope.weather = weather; // Response Handler #3
// Let's force an error to demonstrate the reportProblem() works!
throw( new Error("Just to prove catch() works! ") );
}));
},
reportProblems = function( fault )
{
$log.error( String(fault) );
};
// 3-easy steps to load all of our information...
// and now we can include logging for of problems within ANY of the steps
loadFlight( user )
.then( parallelLoad )
.catch( reportProblems );
};

这里我们将异常处理也加上了。

扁平化promise调用链(译)的更多相关文章

  1. 学了ES6,还不会Promise的链式调用?🧐

    前言 本文主要讲解promise的链式调用的方法及其最终方案 应用场景 假如开发有个需求是先要请求到第一个数据,然后根据第一个数据再去请求第二个数据,再根据第二个数据去请求第三个数据...一直到最后得 ...

  2. ES6 Promise 的链式调用

    1.什么是Promise Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息. 2.对象的状态不受外界影响.Promise 对象代表一个异步操作,有三种状态: pending: 初始 ...

  3. 面试官:JavaScript如何实现数组拍平(扁平化)方法?

    面试官:JavaScript如何实现数组拍平(扁平化)方法? 1 什么叫数组拍平? 概念很简单,意思是将一个"多维"数组降维,比如: // 原数组是一个"三维" ...

  4. 使用docker-compose 一键部署你的分布式调用链跟踪框架skywalking

    一旦你的程序docker化之后,你会遇到各种问题,比如原来采用的本地记日志的方式就不再方便了,虽然你可以挂载到宿主机,但你使用 --scale 的话,会导致 记录日志异常,所以最好的方式还是要做日志中 ...

  5. Atitit 管理的模式扁平化管理 金字塔 直线型管理 垂直管理 水平管理 矩阵式管理 网状式样管理 多头管理 双头管理

    Atitit 管理的模式扁平化管理  金字塔 直线型管理 垂直管理 水平管理 矩阵式管理 网状式样管理 多头管理 双头管理 1.1. 矩阵管理 1 1.2. 相关信息 矩阵的历史 1 1.3. 基于“ ...

  6. 调用链系列一、Zipkin架构介绍、Springboot集承(springmvc,HttpClient)调用链跟踪、Zipkin UI详解

    1.Zipkin是什么 Zipkin分布式跟踪系统:它可以帮助收集时间数据,解决在microservice架构下的延迟问题:它管理这些数据的收集和查找:Zipkin的设计是基于谷歌的Google Da ...

  7. JS: 数组扁平化

    数组扁平化 什么是数组扁平化? 数组扁平化就是将一个多层嵌套的数组 (Arrary) 转化为只有一层. // 多层嵌套 [1, 2, [3, 4]] // 一层 [1, 2, 3, 4] 递归实现 思 ...

  8. ios 统一设计,iOS6也玩扁平化

    转:http://esoftmobile.com/2014/01/14/build-ios6-ios7-apps/ 前言 前段时间,苹果在它的开发者网站上放出了iOS系统安装比例,其中iOS7占到78 ...

  9. 部署你的分布式调用链跟踪框架skywalking

    使用docker-compose 一键部署你的分布式调用链跟踪框架skywalking https://www.cnblogs.com/huangxincheng/p/9666930.html 一旦你 ...

随机推荐

  1. C#使用Owin技术部署轻量级webApi服务

    写在前面: 除了使用IIS可以启用WebApi之外,微软还提供了Owin技术,免除了IIS繁琐的部署配置,只需要运行编写好的程序,即可启用webApi服务,是不是很爽呢? 对于Owin技术的详细介绍这 ...

  2. jmeter-用户定义的变量

    添加-配置元件-用户定义的变量 请求中出现变量值的位置,用${_tbip}替换 脚本执行完成,在查看结果树中debug sampler中可以看见变量名和变量值

  3. 20175126《Java程序设计》第三学习总结

    # 20175126 2016-2017-2 <Java程序设计>第三周学习总结 ##课余收获——利用JAVA编写最简单的斗地主程序 -由于最近身边的朋友都在玩手机上的斗地主小游戏,我也就 ...

  4. paloalto防火墙的优势

    1.所有 Palo Alto Networks 防火墙提供一个可用于执行防火墙管理功能的带外管理端口 (MGT).通过使用该 MGT 端口,可以将防火墙的管理功能与数据处理功能分开,从而保护对防火墙的 ...

  5. ES查询-match VS match_phrase

    我们以一个查询的示例开始,我们在student这个type中存储了一些学生的基本信息,我们分别使用match和match_phrase进行查询. 首先,使用match进行检索,关键字是“He is”: ...

  6. Win7远程桌面:发生身份验证错误

    下载并安装KB4103718补丁,安装完成后重启计算机 http://www.catalog.update.microsoft.com/Search.aspx?q=KB4103718 根据操作系统选择 ...

  7. Qt5.12.2开发Android环境搭建

    Qt-Android开发环境概要qt-opensource-windows-x86-5.12.2----armv7jdk-8u201-windows-x64android-ndk-r18b-windo ...

  8. (转)Oracle定时执行计划任务

    Oracle定时执行计划任务 在日常工作中,往往有些事情是需要经常重复地做的,例如每天更新业务报表.每天从数据库中提取符合条件的数据.每天将客户关系管理系统中的数据分配给员工做数据库营销……因此我们就 ...

  9. 00.pt-toolkit 目录

    一. 好用便利的工具,常用 pt-align 对齐文本格式pt-archiver 循序渐进的归档表,删除表,迁移数据pt-config-diff 对比不同配置文件.服务器配置参数pt-diskstat ...

  10. 为nginx创建windows服务自启动

    1.下载最新版的 Windows Service Wrapper 程序,比如我下载的名称是 "winsw-1.9-bin.exe",然后,把它命名成你想要的名字(比如: " ...