构建深层嵌套对象的promise链

时间:2017-06-21 05:40:29

标签: javascript node.js es6-promise

我有一个深刻的对象:

{
  "something": "Homepage",
  "else": [
    "[replaceme]",
    "[replaceme]"
  ],
  "aside": "[replaceme]",
  "test": {
    "test": {
      "testing": [
        "[replaceme]",
        "[replaceme]",
        "variable",
        {
          "testing": {
            "testing": {
              "something": "[replaceme]",
              "testing": {
                "testing": [
                  "[replaceme]",
                  "[replaceme]"
                ]
              }
            }
          }
        }
      ]
    }
  }
}

现在我需要用异步函数中的某些内容替换[replaceme]的每一个匹配项。每次都不一样。

我以为我减少了对象的每个级别并返回了一个promise链。

这是我到目前为止所得到的:

const IterateObject = ( object ) => {
    return new Promise( ( resolve, reject ) => {
        Object.keys( object ).reduce( ( sequence, current ) => {
            const key = current;

            return sequence.then( () => {
                return new Promise( ( resolve, reject ) => {

                    if( typeof object[ key ] === 'object' ) {
                        IterateObject( object[ key ] )
                            .then( result => {
                                newObject[ key ] = result;
                        });
                        // ^----- How do I add the next level when it returns a promise?
                    }
                    else {
                        resolve( newObject[ key ] )
                    }
                });
            });
        }, Promise.resolve())
        .catch( error => reject( error ) )
        .then( () => {
            console.log('done');

            resolve();
        });
    });
}

问题

解决此问题的最佳方法是什么?也许承诺链不是正确的工具?

2 个答案:

答案 0 :(得分:2)

不要尝试单一功能。这可能导致常见的反模式知道为The Collection Kerfuffle将此作业拆分为独立的块:深度遍历对象,异步设置器等。通过遍历对象收集所有挂起的promise并使用Promise.all <等待它们全部/ p>

// traverse object obj deep using fn iterator
const traverse = (obj, fn) => {  
  const process = (acc, value, key, object) => {
    const result =
      Array.isArray(value)
        ? value.map((item, index) => process(acc, item, index, value))
        : (
            typeof value === 'object' 
              ? Object.keys(value).map(key => process(acc, value[key], key, value))
              : [fn(value, key, object)]
          )
    return acc.concat(...result)
  }

  return process([], obj)
}

&#13;
&#13;
// fake async op
const getAsync = value => new Promise(resolve => setTimeout(resolve, Math.random()*1000, value))

// useful async setter
const setAsync = (target, prop, pendingValue) => pendingValue.then(val => target[prop] = val)

// traverse object obj deep using fn iterator
const traverse = (obj, fn) => {  
  const process = (acc, value, key, object) => {
    const result =
      Array.isArray(value)
        ? value.map((item, index) => process(acc, item, index, value))
        : (
            typeof value === 'object' 
              ? Object.keys(value).map(key => process(acc, value[key], key, value))
              : [fn(value, key, object)]
          )
    return acc.concat(...result)
  }
  
  return process([], obj)
}

// set async value
const replace = (value, prop, target) => {
  if( value === '[replaceme]') {
    return setAsync(target, prop, getAsync(`${prop} - ${Date.now()}`))
  }
  return value
}
   
const tmpl = {
  "something": "Homepage",
  "else": [
"[replaceme]",
"[replaceme]"
  ],
  "aside": "[replaceme]",
  "test": {
"test": {
  "testing": [
    "[replaceme]",
    "[replaceme]",
    "variable",
    {
      "testing": {
        "testing": {
          "something": "[replaceme]",
          "testing": {
            "testing": [
              "[replaceme]",
              "[replaceme]"
            ]
          }
        }
      }
    }
  ]
}
  }
}

Promise.all(
  traverse(tmpl, replace)
)
.then(() => console.log(tmpl))
.catch(e => console.error(e))
&#13;
&#13;
&#13;

或者你可能想看一下async/await,它可以让你写更多&#34;同步和#34;码。但是这种实现会导致顺序执行。

    // using async/await
    const fillTemplate = async (tmpl, prop, object) => {
      if(Array.isArray(tmpl)) {
        await Promise.all(tmpl.map((item, index) => fillTemplate(item, index, tmpl)))
      }
      else if(typeof tmpl === 'object') {
        await Promise.all(Object.keys(tmpl).map(key => fillTemplate(tmpl[key], key, tmpl)))
      }
      else {
        await replace(tmpl, prop, object) 
      }
    }

&#13;
&#13;
    const tmpl = {
  "something": "Homepage",
  "else": [
    "[replaceme]",
    "[replaceme]"
  ],
  "aside": "[replaceme]",
  "test": {
    "test": {
      "testing": [
        "[replaceme]",
        "[replaceme]",
        "variable",
        {
          "testing": {
            "testing": {
              "something": "[replaceme]",
              "testing": {
                "testing": [
                  "[replaceme]",
                  "[replaceme]"
                ]
              }
            }
          }
        }
      ]
    }
  }
}
    
    // fake async op
    const getAsync = value => new Promise(resolve => setTimeout(resolve, Math.random()*1000, value))

    // useful async setter
    const setAsync = (target, prop, pendingValue) => pendingValue.then(val => target[prop] = val)

    // set async value
    const replace = (value, prop, target) => {
      if( value === '[replaceme]') {
        return setAsync(target, prop, getAsync(`${prop} - ${Date.now()}`))
      }
      return value
    }

    // using async/await
    const fillTemplate = async (tmpl, prop, object) => {
      if(Array.isArray(tmpl)) {
        await Promise.all(tmpl.map((item, index) => fillTemplate(item, index, tmpl)))
      }
      else if(typeof tmpl === 'object') {
        await Promise.all(Object.keys(tmpl).map(key => fillTemplate(tmpl[key], key, tmpl)))
      }
      else {
        await replace(tmpl, prop, object) 
      }
    }
    
    Promise
      .resolve(fillTemplate(tmpl))
      .then(() => console.log(tmpl))
      .catch(e => console.error(e))
      
&#13;
&#13;
&#13;

答案 1 :(得分:1)

我认为这与你要做的事情是一致的。

var object = 
{
  "something": "Homepage",
  "else": [
    "[replaceme]",
    "[replaceme]"
  ],
  "aside": "[replaceme]",
  "test": {
    "test": {
      "testing": [
        "[replaceme]",
        "[replaceme]",
        "variable",
        {
          "testing": {
            "testing": {
              "something": "[replaceme]",
              "testing": {
                "testing": [
                  "[replaceme]",
                  "[replaceme]"
                ]
              }
            }
          }
        }
      ]
    }
  }
}

function parseObject(object, promises){
    var keys = Object.keys(object);
    for(let i = 0; i < keys.length; i++){
        let item = object[keys[i]];
        if(item instanceof Array){
            for(let k = 0;k < item.length; k++){
                if(typeof(item[k]) === "object") {
                    parseObject(item[k], promises);
                }
                else{
                    promises.push(
                        magicFunction(
                            item[k], 
                            (newValue) => item[k] = newValue
                        )
                    );
                }
            }
        }
        else if(typeof(item) === "object")
            parseObject(item, promises);
        else
            promises.push(magicFunction(item , (newValue) => object[keys[i]] = newValue));
    }

    return;
}

function magicFunction(item, defferedAssignment){
    return new Promise(( resolve, reject ) => {
        if(item === "[replaceme]") {
            // Some ajax or misc
            setTimeout(() => {
                defferedAssignment("Replaced");
                resolve()
            }, Math.floor(Math.random() * 1000));
        }
        else {
            defferedAssignment(item);
            resolve()
        }
    });
}

var promises = [];
parseObject(object, promises);
Promise.all(promises).then(() => console.log(object))

我不知道这是否是最好的方法,但它对我有用:)。

你也可以将promiseAll包装成一个函数。

此外,这还要求magicFunction的结果不会返回另一个[replaceme]。如果这不是所需的选项,则需要进行微调。