首页 > 解决方案 > Javascript Nested For Loop 在返回数组时不更新数组

问题描述

let posts = [];
for(var i = user.posts.length - 1; i >= 0; i--){
  posts.push(user.posts[i]);
}
for(var i = 0; i < user.friends.length; i++){
 let query = {username:user.friends[i]};
 User.findOne(query, function(err, theuser){
  if(err) throw err;

  if(theuser){
    if(theuser.posts.length > 0){
      for(var j = theuser.posts.length - 1; j >= 0; j--){
        posts.push(theuser.posts[j]);
      }
    }
  }
});
} 
return posts;

所以我的问题是,当我调用这个函数时,帖子没有正确返回。当我运行这个时,我想返回一个帖子数组,第一个 for 循环运行良好,但是当我想将一个元素从 var = j 的嵌套 for 循环推送到数组时,它不会更新帖子数组。我认为它可能与异步功能有关,但我不确定如何在我的情况下实现它。

标签: javascriptnode.jsasynchronousasync-awaitnested

解决方案


不能从异步函数返回;至少不像你正在做的那样。

let posts = []
// ...
for(var i = 0; i < user.friends.length; i++){
  // ...
  User.findOne(query, function(err, theuser){
    // nothing here happens before `return posts` is called below
  })
} 
return posts;

这应该可以帮助您回到正确的道路上-

// reverse copy of user posts
const posts =
  [ ...user.posts ].reverse()

// "promisified" findOne
const findOne = query =>
  User.findOne
    ( query
    , (err, res) =>
        err
          ? Promise.reject(err)
          : Promise.resolve(res)
    )

Promise
  .all(user.friends.map(username => findOne({ username }))
  .map(friend => friend.posts.reverse())
  .then(friendPosts => posts.concat(friendPosts)
  .then
     ( allPosts =>
         // do something with all posts here
     )

替代方案是async/ await。这里我们也使用util.promisify代替findOne手工重写——

const { promisify } =
  require('util')

const findOne =
  promisify(User.findOne)

// below, `async` functions always return a promise
const allPostsAndFriendsPosts = async (user) =>
{ // write normal synchronous code here
  let res =
    [ ...user.posts ].reverse()

  // inside async functions, we can `await` a promise
  const friends =
    await Promise.all(user.friends.map(username => findOne({ username }))

  // then use the promised value synchronously
  for (const f of friends)
    res = res.concat(f.posts.reverse())

  return res // <-- returns a promise
}

allPostsAndFriendsPosts(user)
  .then
    ( allPosts =>
        // do something with all posts
    )

async并且await是这个时代的双人组合。我只想对他们的综合能力表示赞赏。假设您有一个数据库-

const DB = 
  { '/0': { a: 'a', _link: '/1' }
  , '/1': { b: 'b', _link: '/2' }
  , '/2': { c: 'c', d: 'd', _link: '/3' }
  , '/3': { e: 'e' }

  , '/4': { f: 'f', _link: '/5' }
  , '/5': { g: 'g' }

  // ...
  }

每个节点都有一个类似/0,/1等的路径,并且节点可以使用该_link属性链接到其他节点。link->link->link 的链没有长度限制。给定一个起始节点,目标是产生整个节点序列——</p>

recursiveGet ('/0') .then (console.log, console.error)
// [ { a: 'a' }, { b: 'b' }, { c: 'c', d: 'd' }, { e: 'e' } ]

recursiveGet ('/4') .then (console.log, console.error)
// [ { f: 'f' }, { g: 'g' } ]

recursiveGet ('/99') .then (console.log, console.error)
// Error: path not found: /99

我们需要某种方式来定义一个循环,某种方式来初始化它,某种方式来执行下一个循环,最后是某种方式来说明循环何时完成。哦,一切都必须是异步的。

这是一项艰巨的任务,但async可以await胜任这项任务。在编写泛型函数时,我们尽可能保持泛型以最大化可重用性 -

const asyncUnfold = async (loop, init) =>

  // call the user's loop with
  loop

      // the "next" function
      // accepts two arguments
      // 1. the item to add to the result
      // 2. the next accumulator
    ( async (x, acc) =>
        // the item is prepended to the recursive result
        [ x, ...await asyncUnfold (f, acc) ]

      // the "done" function
      // accepts one argument
      // 1. then final item of the result
    , async (x) => [ x ]

      // initial accumulator
    , init
    )

给定一个执行递归查询的get函数-

const get = async (url = '') =>
  fetch (url) .then (res => res .json ())

我们现在可以recursiveGet使用asyncUnfold-

const recursiveGet = async (initUrl) =>

  // use our new magic wand
  asyncUnfold

      // our loop
      // receives 3 arguments
      // 1. the "next" function
      // 2. the "done" function
      // 3. the accumulator
    ( async (next, done, { _link, ...res }) =>

        // if we have a _link ...
        _link

          // add res to the output
          // the next step is get(_link)
          ? next (res, await get (_link))

          // otherwise there is no _link
          // call done with the last result
          : done (res)

    // initial accumulator
    , await get (initUrl)
    )

所有这些都无需触摸Promise, reject, resolve, 或then。我希望这能让您了解可以使用asyncand进行的强大表达await。在下面的浏览器中验证结果 -

const asyncUnfold = async (f, init) =>
  f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
    , async (x) => [ x ]
    , init
    )
    
const get = async (url = '') =>
  fetch (url) .then (res => res .json ())
  
const recursiveGet = async (initUrl) =>
  asyncUnfold
    ( async (next, done, { _link, ...res }) =>
        _link
          ? next (res, await get (_link))
          : done (res)
    , await get (initUrl)
    )
 
const DB = 
  { '/0': { a: 'a', _link: '/1' }
  , '/1': { b: 'b', _link: '/2' }
  , '/2': { c: 'c', d: 'd', _link: '/3' }
  , '/3': { e: 'e' }
  , '/4': { f: 'f', _link: '/5' }
  , '/5': { g: 'g' }
  }

// fake fetch for demo
const fetch = (url = '') =>
  DB[url] === undefined
    ? Promise .reject (Error(`path not found: ${url}`)) .then (delay)
    : Promise .resolve ({ json: () => DB[url] }) .then (delay)

// fake delay for demo
const delay = (x, ms = 250) =>
  new Promise (r => setTimeout (r, ms, x))
  
recursiveGet ('/0') .then (console.log, console.error)
// [ { a: 'a' }, { b: 'b' }, { c: 'c', d: 'd' }, { e: 'e' } ]


推荐阅读