递归承诺

时间:2018-03-27 23:28:20

标签: javascript recursion promise

我已经看过几个关于promises中递归的问题,并对如何正确实现它们感到困惑:

我把一个简单的例子放在一起(见下文) - 这只是一个例子,所以我可以理解如何使用promises工作进行递归,而不是代表我正在工作的代码。

Net-net,我想要解决的承诺,但根据节点上的输出,它无法解决。有关如何解决问题的任何见解?

var i = 0;

var countToTen = function() { 
    return new Promise(function(resolve, reject) {
        if (i < 10) {
            i++;
            console.log("i is now: " + i);
            return countToTen();
        }
        else {
            resolve(i);
        }
    });
}

countToTen().then(console.log("i ended up at: " + i));

控制台上的输出:

> countToTen().then(console.log("i ended up at: " + i));
i is now: 1
i is now: 2
i is now: 3
i is now: 4
i is now: 5
i is now: 6
i is now: 7
i is now: 8
i is now: 9
i is now: 10
i ended up at: 10
Promise { <pending> }
承诺永远不会解决。

4 个答案:

答案 0 :(得分:9)

如果只要i小于10就查看代码,那么您将递归并且永远不会解决承诺。你最终解决了一个承诺。但这不是初始来电者的承诺。

您需要使用递归返回的承诺来解决。如果您使用承诺解决该系统的工作原理,它将无法解决,直到该值得到解决:

let i = 0;
const countToTen = () => new Promise((resolve, reject) => {
    if (i < 10) {
      i++;
      console.log("i is now: " + i);
      resolve(countToTen());
    } else {
      resolve(i);
    }
  });

countToTen().then(() => console.log("i ended up at: " + i));

最后一部分也有错误。你没有为then提供一个函数,所以如果你已经做了一些实际上会等待的东西,你将首先获得"i ended up at: 0"

答案 1 :(得分:1)

如果你将i作为函数的参数而不依赖于外部状态会更好

const countToTen = (i = 0) =>
  new Promise ((resolve, _) =>
    i < 10
      ? (console.log (i), resolve (countToTen (i + 1)))
      : resolve (i))
      
      
countToTen () .then (console.log, console.error)
// 0 1 2 3 4 5 6 7 8 9 10

如果您将10作为参数

,那就更好了

const countTo = (to, from = 0) =>
  new Promise ((resolve, _) =>
    from < to
      ? (console.log (from), resolve (countTo (to, from + 1)))
      : resolve (from))

countTo (7, 2) .then (console.log, console.error)
// 2 3 4 5 6 7

更通用的方法是反向折叠 - 或unfold

const unfold = (f, init) =>
  f ( (x, acc) => [ x, ...unfold (f, acc) ]
    , () => []
    , init
    )

const countTo = (to, from = 0) =>
  unfold
    ( (next, done, acc) =>
        acc <= to
          ? next (acc, acc + 1)
          : done ()
    , from
    )

console.log (countTo (10))
// [ 0, 1, 2, 3, 4, 5, 6,  7, 8, 9, 10 ]

console.log (countTo (7, 2))
// [ 2, 3, 4, 5, 6, 7 ]

但是你想要一个异步展开,asyncUnfold。现在,用户提供的函数f可以是异步的,我们得到所有收集值的承诺

const asyncUnfold = async (f, init) =>
  f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
    , async () => []
    , init
    )

const delay = (x, ms = 50) =>
  new Promise (r => setTimeout (r, ms, x))

const countTo = (to, from = 0) =>
  asyncUnfold
    ( async (next, done, acc) =>
        acc <= to
          ? next (await delay (acc), await delay (acc + 1))
          : done ()
    , from
    )

countTo (10) .then (console.log, console.error)
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

countTo (7, 2) .then (console.log, console.error)
// [ 2, 3, 4, 5, 6, 7 ]

这是一个更实际的例子,我们有一个记录数据库,我们希望执行递归查找,或者某事......

  • db.getChildren接受节点id并仅返回节点的直接子项

  • traverse接受一个节点id,并以递归方式提取所有后代子节点(按深度优先顺序排列)

const data =
  { 0 : [ 1, 2, 3 ]
  , 1 : [ 11, 12, 13 ]
  , 2 : [ 21, 22, 23 ]
  , 3 : [ 31, 32, 33 ]
  , 11 : [ 111, 112, 113 ]
  , 33 : [ 333 ]
  , 333 : [ 3333 ]
  }

const db =
  { getChildren : (id) =>
      delay (data [id] || [])
  }

const Empty =
  Symbol ()

const traverse = (id) =>
  asyncUnfold
    ( async (next, done, [ id = Empty, ...rest ]) =>
        id === Empty
          ? done ()
          : next (id, [ ...await db.getChildren (id), ...rest ])
    , [ id ]
    )

traverse (0) .then (console.log, console.error)
// [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]

答案 2 :(得分:0)

尽量不要在函数中使用共享可变状态(特别是当它们是异步时)。您正在使用window.i但是任何内容都可以更改该值,因为i值仅在您的函数中用作计数器,所以不需要这样做:

const later = (milliseconds,value) =>
  new Promise(
    resolve=>
      setTimeout(
        ()=>resolve(value),
        milliseconds
      )
  );

const countTo = toWhat => {
  const recur = counter =>
    later(1000,counter)
    .then(
      i=>{
        console.log(`i is now: ${i}`);
        return (i<toWhat)
          ? recur(i+1)
          : i;
      }
    )
  return recur(1);
}

countTo(10)
.then(
  i=>console.log(`i ended up at: ${i}`)
);

答案 3 :(得分:0)

许多成员已经提到,需要解决递归返回的承诺。

我想将代码共享为async/await语法。

const printNumber = (i) => console.log("i is now: " + i);

// recursive function to call number increment order
const recursiveCallNumber = async (i, checkCondition) => {
    // if false return it, other wise continue to next step
    if (!checkCondition(i)) return;
    // then print
    printNumber(i); 
     // then call again for print next number
    recursiveCallNumber(++i, checkCondition);
}

await recursiveCallNumber(1, (i) => i <= 10);