递归承诺 - 有时它起作用,有时它不起作用

时间:2016-08-26 19:51:28

标签: javascript recursion promise

我有一个递归函数设置来重新创建/加载propertyTemplate数组。

第一个函数给出一个这样的对象:

 {   
    staffedLocation: ['schedulingGroup',{property: 'staff',subProperties: ['description']}],
    staff: true,
    assignedShifts: true,
    editedShifts: true,
    deletedShifts: true,
    unassignedShifts: true,
    rangeStart: true,
    rangeEnd: true
}

我需要将这个对象重新创建为一个对象数组,以便正确填充我的UI:

[
    {checked: null, name: "staffedLocation", properties: [
        {checked: null, name: "oid", properties: null, toggle: null, type: "integer"},
        {checked: null, name: "_class", properties: null, toggle: null, type: "string"},
        {checked: true, name: "schedulingGroups", properties: null, toggle: null, type: "list"},
        {checked: null, name: "staff", properties: [
            {checked: null, name: "oid", properties: null, toggle: null, type: "integer"},
            {checked: null, name: "_class", properties: null, toggle: null, type: "string"},
            {checked: true, name: "description", properties: null, toggle: null, type: "string"},
            {checked: null, name: "limits", properties: null, toggle: null, type: "list"},
            {checked: null, name: "weeklyMaxHours", properties: null, toggle: null, type: "integer"}

        ], toggle: true, type: "list"},
    ], toggle: true, type: "staffedLocation"},
    {checked: null, 
    name: "staff", properties: [
        {checked: null, name: "oid", properties: null, toggle: null, type: "integer"},
        {checked: null, name: "_class", properties: null, toggle: null, type: "string"},
        {checked: null, name: "description", properties: null, toggle: null, type: "string"},
        {checked: null, name: "limits", properties: null, toggle: null, type: "list"},
        {checked: null, name: "weeklyMaxHours", properties: null, toggle: null, type: "integer"}
    ], toggle: true, type: "staff"},
    {checked: null, name: "assignedShifts", properties: null, toggle: null, type: "shiftForEmail"},
    {checked: null, name: "editedShifts", properties: null, toggle: null, type: "shiftForEmail"},
    {checked: null, name: "deletedShifts", properties: null, toggle: null, type: "shiftForEmail"},
    {checked: null, name: "unassignedShifts", properties: null, toggle: null, type: "shiftForEmail"},
    {checked: null, name: "rangeStart", properties: null, toggle: null, type: "timestamp"},
    {checked: null, name: "rangeEnd", properties: null, toggle: null, type: "timestamp"}
]

目前,当我调用convert函数时,它会遍历并开始创建我的对象数组。但是,当它命中convertRecurse时,会创建一个promise以收集其中一个项的属性。

最初我认为在收集所有属性之后嵌套promise中的所有内容都会正常运行该函数。但是,当我第一次转到页面并加载模板时,属性不会出现。如果我再次重新打开模板,它会起作用。

我认为链接承诺会导致第二个承诺等待继续前进。但是,我对他们的工作原理并不了解。

function convert(template){
    $scope.propertyTemplate = [];
    for(var k in template){
        var obj = {};
        obj.checked = null;
        obj.name = k;

        if(template[k] !== true){
            convertRecurse(template[k], obj, k);
        } else {
            obj.properties = null;
            obj.toggle = null;
        }

        $scope.propertyTemplate.push(obj);
    }
}

function convertRecurse(array, obj, parent){

    var propArr = [];

    var namespace = new namespaceFactory(parent);
    namespace.init();

    namespace.fetchProperties().then(function(props){
        props.forEach(function(prop){

            var fetchObj = {};
            fetchObj.name = prop.property;
            fetchObj.type = prop.type;
            fetchObj.checked = null;
            fetchObj.properties = null;
            fetchObj.toggle = null;

            propArr.push(fetchObj);
        });

        return propArr;

    }).then(function(){

        var objArr = [];

        for(var j = 0; j < array.length; j++){

            if(typeof array[j] === 'object'){
                objArr.push(array[j]);
                array.splice(j, 1);
                j--;
            }

            for(var i = 0;i < propArr.length; i++){

                if(array[j] === propArr[i].name){
                    propArr[i].checked = true;
                } 
            }
        }

        for(var k = 0; k < objArr.length; k++){
            var propObj = {};
            propObj.checked = null;
            propObj.name = objArr[k].property;

            if(objArr[k].subProperties){
                convertRecurse(objArr[k].subProperties, propObj, objArr[k].property)
                for(var x = 0; x < propArr.length; x++){
                    if(propArr[x].name === propObj.name){
                        propArr.push(propObj);
                        propArr.splice(x, 1);

                    }
                }
            } else {
                propObj.properties = null;
                propObj.toggle = null;
            }
        }

        obj.properties = propArr;
        obj.toggle = true;

    }).catch(console.log.bind(console));
}

我的问题是,当我第一次调用此函数时,它不会加载。

enter image description here

但是,如果我再次打电话,一切似乎都能完美运作。

enter image description here

1 个答案:

答案 0 :(得分:1)

您遇到的问题涉及异步流程。当前编写的代码将convertRecurse()视为同步,而实际上是异步的。

代码很难在某些地方使用,但棘手的部分是同步转换,我并未尝试理解。

修复非常广泛,部分原因是异步操作发生在循环中。更具体地说,您需要:

  • 通过构建.then()链或
  • 来顺序执行异步操作
  • 并行执行异步操作并汇总承诺

正如您将在下面看到的那样,异步流程的一个主要方面是全面地获得适当的回报。

这是采用顺序方法:

function convert(template) {
    $scope.propertyTemplate = [];

    // Make array of template's enumerable keys
    // See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
    var keys = Object.keys(template);

    // Build a .then() chain from `keys`.
    return keys.reduce(function(promise, k) {
        return promise.then(function() {
            var obj = {
                name: k,
                checked: null,
                properties: null,
                toggle: null
            };
            if(template[k] !== true) {
                return convertRecurse(template[k], obj, k);
            } else {
                return obj;
            }
        }).then(function(obj_) {
            $scope.propertyTemplate.push(obj_);
            return obj_; // make obj_ available to the caller
        });
    }, $q.resolve()); // $q is assumed
}

function convertRecurse(array, obj, parent) {
    var namespace = ...
    return namespace.fetchProperties().then(function(props) {
        var propArr = ... // some synchronous transform of (props)
        var objArr = ... // some synchronous transform of (array, propArr)

        // Build a .then() chain from `objArr`
        return objArr.reduce(function(promise, o) {
            return promise.then(function() {
                if(o.subProperties) {
                    var propObj = {
                        name: o.property,
                        checked: null,
                        properties: null,
                        toggle: null
                    };
                    return convertRecurse(o.subProperties, propObj, o.property).then(function() {
                        propArr = ... // some synchronous transform of (propArr, propObj)
                    });
                }
            });
        }, $q.resolve())
        .then(function() {
            obj.properties = propArr;
            obj.toggle = true;
            return obj; // deliver the augmented obj back to the caller.
        });
    }).catch(function(error) {
        console.log(error);
        throw error;
    });
}

除了我自己的错误(根本不可能),您需要做的就是重新插入三个转换的代码,为了整体流程的清晰,省略了这三个转换的代码。就个人而言,我会将变换写为函数,并将代码保持原样。