2014-11-20 3 views
2

Вот обратный вызов (слегка измененный/упрощенный для целей этого вопроса), который я написал для обработки некоторых данных объекта.Сглаживание вложенного обратного вызова

function (err, data) { 
    var rv; 
    if (data && data.result instanceof Array) { 
     rv = data.result.map(function (value) { 
     if (value && value.track_contributions instanceof Array) { 
      return value.track_contributions.map(function (innerValue) { 
      if (innerValue && innerValue.track) { 
       return innerValue.track.mid; 
      } 
      }); 
     } 
     }); 
     // flatten nested arrays 
     rv = [].concat.apply([], rv); 
    } 
    console.log(rv); 
    }; 

Вот что объект, который я обработки выглядит следующим образом:

{ 
    "result": [ 
    { 
     "type": "/music/artist", 
     "track_contributions": [ 
     { 
      "track": { 
      "mid": "/m/015rm3l" 
      } 
     }, 
     { 
      "track": { 
      "mid": "/m/0nm2km" 
      } 
     }, 
     { 
      "track": { 
      "mid": "/m/010ksbq" 
      } 
     }, 
     ... 
     ] 
    } 
    ] 
} 

И я хочу, возвращаемое значение из моего обратного вызова, который выглядит следующим образом:

[ '/m/015rm3l', 
    '/m/0nm2km', 
    '/m/010ksbq', 
    ... 
] 

Мой код работает просто отлично, но я чувствую, что все это вложение - это запах кода.

Как я могу сделать этот тип кода более лёгким, более читаемым и поддерживаемым, и все это замечательно? Обещания? Некоторые утилиты типа lodash? Что-то другое? Все вышеперечисленное?

+0

Этот подход приведет к множеству «неопределенных» значений, если условия в функциях 'map' неверны. Потому что в тех случаях ничего не возвращается. – Amberlamps

+0

@Amberlamps Правильно. Мой код ожидает этого. Но да, стоит отметить! – Trott

ответ

1

Вот версия, которую я думаю, проще понять и поддерживать, не дублированного кода, поскольку он использует общий обход с обратным вызовом, чтобы добавить специфику вашей структуры данных:

function process(err, data) { 
    var rv = []; 
    function doObject(obj, key, callback) { 
     if (obj[key] && obj[key] instanceof Array) { 
      obj[key].forEach(function(value) { 
       callback(value); 
      }); 
     } 
    } 
    doObject(data, "result", function(result) { 
     doObject(result, "track_contributions", function(item) { 
      if (item.track && item.track.mid) { 
       rv.push(item.track.mid); 
      } 
     }); 

    }); 
    console.log(rv); 
} 

Без изменения базовый алгоритм, который вы используете, вы можете инициализировать rv и просто подталкивать результаты непосредственно в rv, а не создавать подмассивы, которые затем должны быть сплющены.

function process(err, data) { 
    var rv = []; 
    if (data && data.result instanceof Array) { 
     data.result.forEach(function(value) { 
      if (value && value.track_contributions instanceof Array) { 
       value.track_contributions.forEach(function(innerValue) { 
        if (innerValue && innerValue.track) { 
         rv.push(innerValue.track.mid); 
        } 
       }); 
      } 
     }); 
    } 
    console.log(rv); 
} 

Поскольку имена свойств, которые вы ищете на каждом уровне разные, это не так эффективно и легко использовать рекурсию, чтобы избежать повторного кода (хотя это вариант).

Вот двухуровневый рекурсивный алгоритм, хотя я не думаю, что добавленная сложность на самом деле стоит того. Если вы собираетесь более 2-х уровней глубоко или произвольное число уровней может иметь больше смысла:

function process(err, data) { 
    var rv = []; 
    function doArray(item, key1, key2) { 
     if (item && item[key1] instanceofArray) { 
      item[key1].forEach(function(value) { 
       if (key2) { 
        doArray(value, key2); 
       } else if (value && value.track.mid) { 
        rv.push(value.track.mid); 
       } 
      }); 
     } 
    } 
    doArray(data, "result", "track_contributions"); 
    console.log(rv); 
} 
+0

Добавлен рекурсивный алгоритм. – jfriend00

+0

Это интересное решение, но это не более удобочитаемо и не поддерживается. Кроме того, рекурсия здесь совершенно не нужна. – Amberlamps

+0

@Amberlamps - см. Примечания о рекурсивном решении в моем ответе. IMO, мое первое решение проще понять, чем OP (нажатие результатов непосредственно в массив результатов, а не на создание вложенных массивов) и немного короче. Не большой выигрыш, но в этом коде не так много общего, так что я мог найти. – jfriend00

1

Такой подход поможет вам желаемый результат:

function getArray(err, data) { 

    var rv; 

    function mapArray(obj, array, callback) { 
    if (obj && obj[array] instanceof Array) { 
     return obj[array].map(callback); 
    } 
    } 

    function getValue(value) { 
    return mapArray(value, 'track_contributions', getInnerValue); 
    } 

    function getInnerValue(innerValue) { 
    return innerValue.track && innerValue.track.mid; 
    } 

    rv = [].concat.apply([], mapArray(data, 'result', getValue)); 

    console.log(rv); 

}; 

getArray(null, data); 

Ключ должен разделить каждый функции в желаемую цель и повторное использование каждой функции, которая повторяется.

Отображение, например, выполняется дважды. Поэтому давайте напишем общую функцию mapArray. Он ожидает obj, имя атрибута массива array и обратный вызов.

Для внешней функции в вашем примере у нас есть mapArray(data, 'result', getValue), а для внутренней функции это mapArray(value, 'track_contributions', getInnerValue).

Внутри функции getValue мы определяем внешнюю функцию, а getInnerValues определяет внутреннюю функцию.

Выполнено.

0

Вы смешиваете обход структуры данных с логикой домена, применяемой на ее узлах, следовательно, запах кода.Лучше разделить оба аспекта, имея общий алгоритм обхода, предоставляемый функцией домена (в вашем текущем случае коллекционер).

Например:

function traverse(source, collector) { 
    for(var property in source) { 
     var value = source[property]; 
     if (value instanceof Array) { 
     for(var ii=0; ii<value.length; ii++) { 
      arguments.callee(value[ii], collector); 
     } 
     } else if (value instanceof Object){ 
      arguments.callee(value, collector); 
     } else { 
      collector(property, value); 
     } 
    } 
    } 

    var values=[]; 
    traverse(source, function(property, value) { 
    if (property == "mid") { 
     values.push(value); 
    } 
    }); 
    document.write(values.join(", ")); 

Алгоритм обхода может быть более сложным, в зависимости от правил, ограничивающих исходную структуру данных; не забудьте сохранить его в конце (т. е. не писать код, который вы не будете использовать).

+0

Не будет ли это просто собирать все свойства 'mid', независимо от того, где они находятся в структуре данных? И, не будет ли это также итерировать любые перечислимые свойства на массивах? И, не будет ли это также терпеть неудачу в режиме «строгого»? – jfriend00

+0

1) Да, они будут собирать все «средние» свойства, потому что у меня создалось впечатление, что это была цель OP и хотелось, чтобы код был минимальным. Можно легко изменить алгоритм обхода, например, собрать и передать полный путь свойства (и не только последний бит), если это требование. – Elegie

+0

Что касается других замечаний: «for in» будет перебирать перечислимые свойства, что мы хотим здесь. Код действительно несовместим со «строгим режимом», потому что он использует arguments.callee. В обоих случаях, если ваши требования разные, тогда не стесняйтесь настраивать код для их удовлетворения! Чтобы удалить аргумент.callee, вы можете напрямую обратиться к именованному свойству, связанному с объявлением функции (немного менее чистому, так как он обращается к внешнему произвольному имени переменной) или изменить функцию на именованное функциональное выражение (с обычным браузером предостережение). – Elegie

Смежные вопросы