【问题标题】:Angular UI router doesn't process the resolve function when i use async/await feature?当我使用异步/等待功能时,Angular UI 路由器不处理解析功能?
【发布时间】:2017-08-24 13:54:24
【问题描述】:

我一直在尝试根据这个article呈现与状态和组件相关的某些模板

在我在 dev-server 下运行的项目中,一切正常,当我执行 $state.go("home") 时,组件模板按我的预期加载,但是当我在测试环境中执行此操作时,这不起作用。

之前,在测试中,当我使用“旧方式”使用“模板”而不是“组件”和 ui-router 时,执行 $rootScope.$digest() 足以在 <div ui-view></div> 内添加模板,但使用这种新方式不再起作用了。

我做错了什么?

编辑:我一直在尝试深入了解问题,发现问题与已完成的 HTTP 请求有关。也许这与我的承诺在使用 async/await 的解析回调上解析的方式有关。请检查服务:

服务

export class TodoService {
    constructor($http, BASE_URL) {
        this.http = $http;
        this.url = `${BASE_URL}/todos`
    }
    async getTodos() {
        const apiResponse = await this.http.get(this.url)
        return apiResponse.data.todos
    }
}

路由器

import '@uirouter/angularjs'

export function routes($stateProvider, $locationProvider) {
    $locationProvider.html5Mode({
        enabled: true,
        requireBase: false,
        rewriteLinks: true,
    })

    $stateProvider
        .state("home", {
            url: "/",
            component: "todoList",
            resolve: {
                todosList: TodoService => TodoService.getTodos()
            }
        })
}

测试

import { routes } from "routes"
import { TodoListComponent } from "components/todoList.component"
import { TodoService } from "services/todo.service"

describe("TodoListComponent rendering and interaction on '/' base path", () => {
    let componentDOMelement
    let stateService

    beforeAll(() => {
        angular
            .module("Test", [
                "ui.router"
            ])
            .config(routes)
            .constant("BASE_URL", "http://localhost:5000/api")
            .component("todoList", TodoListComponent)
            .service("TodoService", TodoService)
            //I enable this for better logs about the problem
            .run(['$rootScope','$trace', function($rootScope, $trace) {
               $trace.enable("TRANSITION")
             }])
    })
    beforeEach(angular.mock.module("Test"))

    beforeEach(inject(($rootScope, $compile, $state, $httpBackend) => {
        //build the scene
        //1st render the root element of scene: We needs a router view for load the base path
        let scope = $rootScope.$new()
        componentDOMelement = angular.element("<div ui-view></div>")

        $compile(componentDOMelement)(scope)
        scope.$digest()
        
         document.body.appendChild(componentDOMelement[0]) //This is a hack for jsdom before the $rootScope.$digest() call
        //2nd let's create a fake server for intercept the http requests and fake the responses
        const todosResponse = require(`${__dirname}/../../stubs/todos_get.json`)
        $httpBackend
            .whenGET(/.+\/todos/)
            .respond((method, url, data, headers, params) => {
                return [200, todosResponse]
            })

        //3rd Let's generate the basic scenario: Go at home state ("/" path)
        $state.go("home")
        $rootScope.$digest()
        $httpBackend.flush()
    }))

    it("Should be render a list", () => {
        console.log("HTML rendered")
        console.log(document.querySelectorAll("html")[0].outerHTML)
    })
})

未渲染的 HTML 结果

<html>
<head>
<style type="text/css">
@charset "UTF-8";[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate) {
  display:none !important;
}
ng\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{
  position:absolute;
}
</style>
</head>
<body><!-- uiView: -->
</body>
</html>

另外,我在 HTML 之前跟踪了 stateChange:

console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1276
    Transition #0-0: Started  -> "Transition#0( ''{} -> 'home'{} )"

console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1282
    Transition #1-0: Ignored  <> "Transition#1( ''{} -> 'home'{} )"

console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1313
    Transition #1-0: <- Rejected "Transition#1( ''{} -> 'home'{} )", reason: Transition Rejection($id: 0 type: 5, message: The transition was ignored, detail: "undefined")

我在转换中发现了问题,但没有给出原因。

================================================ ==========================

编辑 2 我们终于找到了问题,但我无法找出真正的问题。我在我的项目中创建了一个分支来显示问题。这与async/await javascript 功能有关:

export class TodoService {
    constructor($http, BASE_URL) {
        this.http = $http;
        this.url = `${BASE_URL}/todos`
    }
    //Interchange the comment on the getTodos method and run `npm run tdd` for see the problem:
    //When async/await doesn't used, the html associated to the resolve in the
    // "/" route that used this service, the promise was resolved that expected.
    //The idea for this branch it's research about the problem and propose a way
    //for we can use async/await on the production code and on the testing environment
    async getTodos() {
        const apiResponse = await this.http.get(this.url)
        return apiResponse.data.todos
    }
    // getTodos() {
    //     return this.http.get(this.url).then(res => res.data.todos)
    // }
}

The repository

所以我的新问题是:

  • 为什么我使用 async/await 功能的方式在测试环境中与 ui-router 解析不兼容,但在生产代码中却可以使用?
  • 可能与 $httpBackend.flush() 调用有关?

编辑 3 Angular UI 路由器存储库中报告的问题3522

【问题讨论】:

  • 听起来问题是 async/await 使用原生承诺,但您的组件需要一个 Angular 承诺(就像 $http 返回的承诺)
  • 我可能只是猜测。我只说我知道的。 Async/await 功能只是一个实验性功能,在非常有限的运行时支持。 Transplitter 使用状态机来模拟 async/await。这个状态机创建简单的承诺,而不是由 $q 服务创建的扩展。尝试比较在不同环境中运行的实际代码。
  • 我注意到您的存储库中没有使用 babel-plugin-transform-async-to-generator。如果你插上它会发生什么?
  • @EduardLepner 使用“transform-regenerator”babel 插件和“stage-3”对于我的生产代码来说已经足够了。我一直在尝试添加 babel-plugin-transform-async-to-generator 但仍然无法正常工作。关于您的另一个响应,如果为运行时创建的承诺不是 $q 服务所期望的承诺,为什么如果我使用我的 npm start 脚本运行代码并在浏览器上看到这项工作,为什么会这样?可能是与 $httpBackend 模拟服务相关的问题,因为这不仅在测试环境中有效?
  • 我已经提取了您的项目并运行了所有测试。它适用于我的机器。

标签: javascript angularjs angular-ui-router async-await


【解决方案1】:

问题是 angular 需要一个 angular Promise,这就是为什么你 then 会工作但你 await 不会,你可以通过使用像 https://www.npmjs.com/package/angular-async-await 这样的库来解决这个问题,或者像他们在这里展示的那样进行构造 @987654322 @

祝你好运!

【讨论】:

    【解决方案2】:

    这只是基于我对resolve-ers 工作方式和ngMock 所做工作的理解的有根据的猜测。在您的第一个示例中,您的 resolve 用于 getTodos 直到 $http 承诺解决后才会返回,此时您从响应中提取值并将其返回。但是,resolve-ers 期望 $q.Promise 值作为标记来保存路由器的渲染,直到它解决。在您的代码中,根据转译方式,awaitreturn 调用可能不会产生正确的标记值,因此它被视为同步响应。

    一种测试方法是在控制器中为您的todolist 组件请求resolve-er 并检查该值。我敢打赌它不是 $q.Promise,尽管它可能是原生 Promise。

    不过,当使用resolve 时,只需通过添加then 来链接Promise 并返回它。路由器会处理剩下的事情。

    或者更好的是,切换到 Observables! (/me鸭子进来的西红柿)

    【讨论】:

    • ????????
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-22
    • 2021-08-23
    • 2020-07-17
    • 1970-01-01
    • 2021-10-05
    相关资源
    最近更新 更多