AngularJS+SailsJS大型应用开发:Services异步逻辑混乱求2018最佳实践
嘿,我完全懂你这种刚上手AngularJS和SailsJS开发大型应用时的混乱感!尤其是从零散的Controller转到Services处理API交互后,异步逻辑突然变得棘手起来——我当年刚接触这俩技术栈的时候也踩过不少类似的坑。结合2018年AngularJS社区的主流实践,给你梳理几个关键方案:
1. 直接复用$http的Promise,别画蛇添足包装$q
你说得对,$http本身就返回Promise,所以在Services里完全没必要手动用$q.defer()去包装请求。直接返回$http的调用结果,让Controller通过.then()和.catch()来处理异步流程就好。
举个Sails API交互的例子:
// user.service.js angular.module('app').service('UserService', function($http) { this.getCurrentUser = function() { // 直接返回$http的Promise return $http.get('/api/user/current') .then(response => { // 在这里可以统一处理Sails的响应格式,比如提取data字段 return response.data; }) .catch(error => { // 先处理通用错误,比如401未授权跳转登录页 if (error.status === 401) { // 这里可以调用路由跳转到登录页,或者触发全局提示 } // 把业务相关的错误抛给Controller处理 return Promise.reject(error.data.message || '获取用户信息失败'); }); }; }); // user.controller.js angular.module('app').controller('UserController', function(UserService, $scope) { UserService.getCurrentUser() .then(user => { $scope.currentUser = user; // 后续业务逻辑,比如加载用户的其他数据 }) .catch(errMsg => { $scope.error = errMsg; }); });
2. 用拦截器统一处理API请求/响应
在大型应用里,每个Service都写重复的错误处理太冗余。2018年的主流做法是用AngularJS的$httpProvider.interceptors来统一处理:
- 请求拦截:给所有API请求加身份验证token(比如存在localStorage里的Sails会话token)
- 响应拦截:统一处理401、404、500等HTTP状态码,弹出全局提示或者跳转页面
示例代码:
angular.module('app').config(function($httpProvider) { $httpProvider.interceptors.push(function($q, $location) { return { request: function(config) { // 给Sails API请求加X-CSRF-Token或者会话token if (config.url.startsWith('/api/')) { config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('sailsToken'); } return config; }, responseError: function(rejection) { if (rejection.status === 401) { // 未授权,跳转到登录页 $location.path('/login'); } else if (rejection.status >= 500) { // 服务器错误,弹出全局提示 alert('服务器出错了,请稍后重试'); } return $q.reject(rejection); } }; }); });
3. 明确职责:Services管数据,Controller管视图
把所有和API交互、数据处理的逻辑都放在Services里,Controller只负责把数据绑定到视图、处理用户交互。比如不要在Service里操作$scope,也不要在Controller里直接写$http请求——这样代码会更清晰,也方便测试。
比如你要加载用户的文章列表,Service负责调用Sails API、过滤无效数据,Controller只负责把列表绑定到视图:
// post.service.js this.getUserPosts = function(userId) { return $http.get(`/api/post/user/${userId}`) .then(res => res.data) .then(posts => { // 统一过滤掉已删除的文章 return posts.filter(post => !post.isDeleted); }); }; // post.controller.js PostService.getUserPosts($scope.currentUser.id) .then(posts => { $scope.userPosts = posts; });
4. 复杂异步场景用$q辅助处理
虽然$http自带Promise,但遇到多个并行请求、或者需要把回调式API转成Promise的场景,$q还是很有用的:
- 并行请求:用
$q.all()同时发起多个API请求,等所有请求完成后再处理结果 - 回调转Promise:如果有一些老的回调函数(比如某些Sails的自定义客户端方法),可以用
$q.defer()包装成Promise
示例并行请求:
// 同时获取用户信息和文章列表 $q.all([ UserService.getCurrentUser(), PostService.getLatestPosts() ]).then(results => { const user = results[0]; const latestPosts = results[1]; // 处理两个请求的结果 }).catch(err => { // 任意一个请求失败都会进入这里 });
5. 避免嵌套Promise,用链式调用保持代码整洁
很多新手容易写出嵌套的.then(),导致代码像Callback Hell一样混乱。正确的做法是用链式调用,每个.then()返回新的Promise或者数据:
❌ 错误示例:
UserService.getCurrentUser() .then(user => { PostService.getUserPosts(user.id) .then(posts => { CommentService.getCommentsForPosts(posts) .then(comments => { // 嵌套太多,难以维护 }); }); });
✅ 正确的链式调用:
UserService.getCurrentUser() .then(user => PostService.getUserPosts(user.id)) .then(posts => CommentService.getCommentsForPosts(posts)) .then(comments => { // 处理最终结果 }) .catch(err => { // 统一处理所有环节的错误 });
最后再提醒一下:2018年AngularJS虽然已经不是最新框架,但社区里的这些实践已经非常成熟了,按照这个思路整理代码,你的异步逻辑会清晰很多,也方便后续维护和扩展。
内容的提问来源于stack exchange,提问作者StS

