通过github API异步递归以获取文件

时间:2017-08-24 18:00:28

标签: javascript node.js github promise github-api

我正在使用github API遍历一个repo并获取其中所有文件的列表。这种结构称为“树”。树基本上是一个子目录。因此,如果我想查看树的内容,我需要对该树的ID发出GET请求。响应将是表示该树中的项目的对象数组。但是这些项目中的一些也将是树,所以我将不得不向该树提出另一个请求。回购可能如下所示:

|src
    app.jsx
    container.jsx
    |client
        index.html
readme.md

此结构将由以下对象

表示
[
    { name:'src', type:'tree', id:43433432 },
    { name:'readme.md', type:'md', id:45489898 }
]
//a GET req to the id of the first object would return the following array:
[
    { name:'app.jsx', type:'file', id:57473738 },
    { name:'contain.jsx', type:'file', id:748433454 },
    { name:'client', type:'tree', id:87654433 }
]
//a GET req to the id of the third object would return the following  array:
[
    { name:'index.html', type:'file', id:44444422 }
]

我需要做的是编写一个函数,它将返回所有文件名称的数组。这非常棘手,因为我正在尝试将异步调用与递归相结合。这是我到目前为止的尝试:

function treeRecurse(tree) {
  let promArr = [];  

  function helper(tree) {    
    tree.forEach(file => {      

      let prom = new Promise((resolve, reject) => {
        if (file.type == `tree`) {
          let uri = treeTrunk + file.sha + `?access_token=${config.ACCESS_TOKEN}`;          

          request({ uri, method: 'GET' })
            .then(res => {
              let newTree = JSON.parse(res.body).tree;              
              resolve(helper(newTree));              
            });

          } else resolve(promArr.push(file.path));
          promArr.push(prom);
      });
    });
  };
  helper(tree);
  Promise.all(promArr)
    .then(resArr => console.log(`treeRecurse - resArr:`, resArr));
};

它正在浏览所有内容,但promArr解析得太快了。另外,我不确定要解决什么问题。哄我。

3 个答案:

答案 0 :(得分:2)

解决方案1:

let username = 'YOUR_USERNAME';
let reponame = 'YOUR_REPONAME';
let access_token = 'YOUR_ACCESS_TOKEN';

const axios = require('axios');

let tree = [];
let fileNames = [];
function start() {
    axios.get(`https://api.github.com/repos/${username}/${reponame}/git/trees/master?access_token=${access_token}`)
      .then(
        function(res) {
          tree = tree.concat(res.data.tree);
          getFilesNameRecur();
        },
        function(err) {
          console.log(err);
        }
      );
}


function getFilesNameRecur() {
  if (tree.length !== 0) {

    let firstObjectOfTree = tree.pop();
    if (firstObjectOfTree.type === 'tree') {
      axios.get(firstObjectOfTree.url + `?access_token=${access_token}`)
        .then(
          function(response) {
            tree = tree.concat(response.data.tree);
            getFilesNameRecur();
          },
          function(err) {
            console.log(err);
          }
        );
    } else if (firstObjectOfTree.type === 'blob') {
      fileNames.push(firstObjectOfTree.path);
      getFilesNameRecur();
    }
  } else {
    // Finished fetching all file names
    console.log(fileNames);
  }
}

start();

解决方案2(首选):

使用ES2017的 async 等待关键字。

文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

import axios from 'axios';

let username = 'YOUR_USERNAME';
let reponame = 'YOUR_REPONAME';
let access_token = 'YOUR_ACCESS_TOKEN';
let tree = [];
let fileNames = [];
async function start() {
  try {
    let res = await axios.get(`https://api.github.com/repos/${username}/${reponame}/git/trees/master?access_token=${access_token}`);
    tree = tree.concat(res.data.tree);
    while (tree.length !== 0) {
      await getFilesNameRecur();
    }
    console.log(fileNames);
  } catch(e) {
    console.log(e);
  }
}

async function getTreeFromGithub(url) {
  try{
    let response = await axios.get(url + `?access_token=${access_token}`);
    return response.data.tree;
  } catch (e) {
    console.log(e);
    throw e;
  }
}


async function getFilesNameRecur() {
  let firstObjectOfTree = tree.pop();
  if (firstObjectOfTree.type === 'tree') {
    let subTree = await getTreeFromGithub(firstObjectOfTree.url);
    tree = tree.concat(subTree);
  } else if (firstObjectOfTree.type === 'blob') {
    fileNames.push(firstObjectOfTree.path);
  }
}

start();

答案 1 :(得分:1)

有趣的问题。正如您可能已经猜到的那样,promArr解析过快的原因是因为只要您将一个Promise推入其中,Promise.all就会通过其条件并且不会等待其他Promise填充在数组中。

我会尝试重新编写它,以便您的递归函数helper接受两个参数treearr - arr是您的Promises数组。首先使用helper(tree, [])调用函数 - 然后在内部,使用必要的promises填充数组,并使用helper(newTree, updatedArray)重新调用帮助程序。添加一些逻辑,用于标识何时完成将承诺填充到updatedArray中,并且在这种情况下,只需返回updatedArray充满您的承诺。

然后只需致电Promise.all(helper(tree, [])).then(...),它应该按预期工作。

有点刚谈过它,但很高兴在你需要的时候实现一些代码。

答案 2 :(得分:1)

让我们模仿Git数据库。

var gitFake = {0       : [{ name:'src', type:'tree', id:43433432 },
                          { name:'readme.md', type:'md', id:45489898 }
                         ],
               43433432: [ { name:'app.jsx', type:'file', id:57473738 },
                           { name:'contain.jsx', type:'file', id:748433454 },
                           { name:'client', type:'tree', id:87654433 }
                         ],
               87654433: [ { name:'index.html', type:'file', id:44444422 }
                         ],
               getDir  : function(id,cb){ setTimeout(cb, 250, !this[id] && "Error: No such directory..!", this[id])}
              };

此库中还包含一个getDir方法,该方法是异步的,并且会返回250ms的目录。我假设gitFake.getDir(id,cb),其中所采用的回调是错误优先类型,如cb(err,data),并未实现。让我们为异步函数发明一个promisifier,接受错误的第一类回调;

function promisify(f){
  return data => new Promise((v,x) => f(data, (err,res) => err ? x(err) : v(res)));
}

现在让我们创建递归异步函数getAllDirs来列出所有嵌套目录;

function promisify(f){ // utility function to promisify the async functions taking error first callback
  return data => new Promise((v,x) => f(data, (err,res) => err ? x(err) : v(res)));
}

function getAllDirs(root = 0){
  gd(root).then(function(ds){
                  ds.length && (console.log(ds),
                                ds.filter( d => d.type === "tree")
                                  .forEach(d => getAllDirs(d.id)));
                })
          .catch(e => console.log(e));
}

var gitFake = {0       : [{ name:'src', type:'tree', id:43433432 },
                          { name:'readme.md', type:'md', id:45489898 }
                         ],
               43433432: [ { name:'app.jsx', type:'file', id:57473738 },
                           { name:'contain.jsx', type:'file', id:748433454 },
                           { name:'client', type:'tree', id:87654433 }
                         ],
               87654433: [ { name:'index.html', type:'file', id:44444422 }
                         ],
               getDir  : function(id,cb){ setTimeout(cb, 250, !this[id] && "Error: No such directory..!", this[id])}
              },
        gd  = promisify(gitFake.getDir.bind(gitFake));

getAllDirs();
.as-console-wrapper { max-height: 100% !important; top: 0; }