AngularJS服务中使用async/await无效的原因是什么?
问题描述
我有一个调用服务的AngularJS控制器,服务执行异步操作,Promise完成后会在控制器的scope对象上设置属性:
function TodoController(TodosService) { let self = this; TodosService.fetchTodos().then((todos) => (self.todos = todos)); // 其他代码 }
但遇到一个问题:服务中用async/await实现时,虽然能成功获取todos,控制器也能设置属性,但视图不会更新;而用then链的写法却能正常渲染待办事项:
app.service("TodosService", function ($http) { // 无效代码:数据能获取,属性能设置,但视图无变化 // this.fetchTodos = async function () { // let json = await $http.get("..."); // return json.data["todos"]; // }; // 有效代码:视图正常更新 this.fetchTodos = () => $http .get("...") .then((json) => json.data["todos"]); });
作为JS新手,我知道async函数和$http.get(...).then(...)都会返回Promise,那两者的区别是什么?我忽略了什么?
核心原因:AngularJS脏检查与Promise类型差异
AngularJS自己实现了一套专属的Promise($q),它和浏览器原生Promise的关键区别在于:
- $q的Promise在resolve时,会自动触发Angular的脏检查机制,让Angular感知到数据变化并更新视图。
- async函数返回的是浏览器原生Promise,原生Promise的回调是在Angular的脏检查周期外执行的——哪怕你更新了控制器的属性,Angular也不会主动检测到这个变化,自然不会更新视图。
你用then链写法时,$http.get()返回的是$q的Promise,它的then回调会在Angular的脏检查周期内执行,所以更新self.todos后视图会自动刷新。但用async/await时,await $http.get()虽然等待的是$q的Promise,但async函数会把最终结果重新包装成原生Promise返回,这个原生Promise的回调脱离了Angular的上下文,导致脏检查没被触发。
解决方法
有两种简单的修复方式:
1. 把原生Promise转为$q的Promise
在服务中注入$q,用$q.resolve()把async函数的返回值包装成Angular能识别的$q Promise:
app.service("TodosService", function ($http, $q) { this.fetchTodos = async function () { let json = await $http.get("..."); return $q.resolve(json.data["todos"]); }; });
2. 手动触发脏检查
在控制器的回调里,用Angular的内置服务手动通知框架更新视图:
- 用
$scope.$apply()直接触发脏检查:
function TodoController(TodosService, $scope) { let self = this; TodosService.fetchTodos().then((todos) => { self.todos = todos; $scope.$apply(); // 手动触发脏检查 }); }
- 用
$timeout(更安全,避免$apply已在执行时抛出错误):
function TodoController(TodosService, $timeout) { let self = this; TodosService.fetchTodos().then((todos) => { $timeout(() => { self.todos = todos; }); }); }
额外说明
AngularJS是依赖脏检查机制的老框架,它的异步操作(比如$http、$timeout、$q)都被封装在自己的上下文里,能自动触发脏检查。如果使用原生异步API(原生Promise、setTimeout等),都需要手动触发脏检查或者把它们包装到Angular的异步上下文里,否则视图不会响应数据变化。
内容的提问来源于stack exchange,提问作者Haidar

