NodeJS 如何正确解决promise?

t3irkdon  于 2023-08-04  发布在  Node.js
关注(0)|答案(3)|浏览(96)

如何确保所有promise都得到正确解决?在这里,即使在函数执行之后,一些语句仍然会被打印出来(我添加了注解的语句)。我觉得getAllRecords没有正确地完成promise。我哪里做错了?

const getAllRecords = async (offset = 0) => { 
  return fetchRecord(offset, 10).then((resp) => { 
    console.log("Called fetchRecord") 
    if (resp.status === 'success') {
       let responseData = resp.data 
       if (responseData.length === 0 { 
         return ({ status: 'success' }) 
       } 
      return proccessData(responseData).then((updateResp) => { 
        if (updateResp.status === 'success') { 
           offset += 10 
           return getAllRecords(offset) 
         } else { 
           return ({ status: 'error' }) 
       }
       }).catch(err => { 
         return ({ status: 'error' }) 
       })
      } else { 
        return ({ status: 'error' }) 
      } 
  }).catch(err => { 
    return ({ status: 'error' }) 
  }) 
} 

const mainFunc = async () => { 
  console.log('Inside mainFunc') 
  return new Promise((resolve, reject) => { 
    return firestore.getAllCities() 
      .then(async (cities) => { 
        return getAllRecords().then(getAllRecordsResponse => { 
          console.log("Called getAllRecords") // 
          if (getAllRecordsResponse.status === 'success') { 
             return getAllRecordsResponse 
           } else { 
             return reject({ status: 'error' }) 
           } 
        }).then((getAllRecordsResponse) => {
             let updateArray = []
             console.log("Updating firestore db") 
             for (const city of cities) { 
                updateArray.push(firebaseDao.updatecityDoc(city)) 
             } 
             return Promise.allSettled(updateArray).then((responseArr) => { 
               let errorArr = [] 
               responseArr.map((item) => 
                 item.status === 'rejected' ? errorArr.push(item.reason) : null 
               ) 
               if (!errorArr.length > 0) { 
                 console.log("done processing") //
                 return resolve({ status: 'success' }) 
               } else { 
                 return resolve({ status: 'error' }) 
               } 
         }) 
      }).catch((err) => { 
        return resolve({ status: 'error' }) 
      }) 
    }).catch((err) => { 
      return resolve({ status: 'error' }) 
    }) 
  }) 
}

字符串
我试图从fetchRecord中的其他地方获取记录,并在processData中处理提取的数据。getAllRecords是一个递归,因为我们不能一次获取所有数据,只能提取10条记录,所以我提取记录,直到我得到一个空的响应。
我不确定的是,承诺在哪里没有得到适当的处理。我是不是漏掉了什么?
有没有一种方法可以只使用promise而不是async/await?

lnlaulya

lnlaulya1#

下面是imo的一个实现,它达到了同样的目的,但有更好的实践,并解决了你的un await艾德promise的问题!请注意,我欺骗了您引用但未包含在问题中的所有函数,因此您可以实际运行此代码:

const getAllRecords = async function*(offset = 0, limit = 10) {
  
  // Yields records from `fetchRecord`, one at a time
  // Records are selected in batches of size `limit`
  
  while (true) {
    // Select latest batch; halt if no items are returned
    const { data: items } = await fetchRecord(offset, limit);    
    if (!items.length) return;
    
    for (const item of items) yield item;

    // Increment the offset; will loop again
    offset += limit;
  }
  
};

const mainFunc = async () => {

  const cities = await firestore.getAllCities();
  
  // Loop through all records yielded by `getAllRecords`; each will result
  // in an "update promise" which we store in an array:
  const updatePromises = [];
  for await (const record of getAllRecords())
    for (const city of cities)
      // TODO: Ignoring `record`??
      updatePromises.push(firebaseDoc.updatecityDoc(city));
  
  // If we want to return all errors which occurred instead of just the
  // first, we need to do some manual error handling - all `updatePromises`
  // have a `.catch` applied, which stores the error
  const settled = await Promise.allSettled(updatePromises);
  
  // A final resulting error has an "errors" property attached to it,
  // exposing the full set of errors which occurred
  const errors = settled.filter(item => item.status === 'rejected');
  if (errors.length) throw Object.assign(Error('Some updates failed'), { errors });
  
  // No return value upon success

};

// Spoofed objects, to make this script runnable:
const firestore = {
  getAllCities: async () => [ 'toronto', 'new york', 'los angeles', 'tokyo' ]
};
const firebaseDoc = {
  updatecityDoc: async (...args) => console.log('Updating city doc; args:', args)
};
const spoofedRecords = 'a'.repeat(30).split('').map((v, i) => ({ recordNum: i }));
const fetchRecord = async (offset, limit) => ({
  data: spoofedRecords.slice(offset, offset + limit)
});

mainFunc()
  .then(() => console.log('Completed successfully'))
  .catch(err => console.log('Encountered fatal error', err));

字符串
一些做法,使这段代码,海事组织,更好:

  • 不要使用返回值来指示错误!始终实现函数,如果它们返回一个值,则表示成功。始终假设如果函数返回一个值,则它成功执行。如果你想表明一个函数失败了,抛出一个Error -永远不要返回类似{ status: 'failed' }的东西!这将使你的代码更精简、健壮、更容易调试!
  • 生成器(function*)用于执行重复批次选择
  • 在许多情况下,如果使用.then使代码变得不那么不可维护,我就删除了它
  • 修复了一些语法错误;“firebaseDao”排印错误已修复
  • 整体结构更容易避免un await艾德promise

这种新格式提出了一个问题:为什么getAllRecords返回的记录被忽略?这不可能是正确的--他们不应该考虑firebaseDoc.updatecityDoc(...)吗?

sq1bmfud

sq1bmfud2#

TL;DR

**不要嵌套异步代码。**这是避免大多数错误的经验法则。

完整版本:

通常情况下,您希望根据另一个异步操作的结果执行一些异步操作,然后执行其他操作。有两种正确的方法,也有很多错误的方法。假设我们有两个Promise返回函数fg,我们需要将f的结果传递给g

错了

嵌套.then处理程序,如示例代码所示:

function doTheThing() {
  f().then((fResult) => {
    g(fResult).then((gResult) => {
      console.log(gResult)
    })
  })
}

字符串

为什么不好

假设不是2步而是10步,每一步都取决于前面的结果。现在,在你得到真正有用的代码之前,你可能会有40个空格的缩进,并且你已经重新创建了可怕的callback hell Promises,它应该保护我们免受所有 Package 器和额外分配带来的更差性能的影响。还有错误处理呢?你只能在单个Promise级别执行.catch,链中的每个.then都是潜在的UnhandledRejectionError。

错了

混合.thenasync/await

function doTheThing() {
  f().then(async (fResult) => {
    const gResult = await g(fResult)
    console.log(gResult)
  })
}

为什么不好

与将doTheThing标记为async并使用await首先获得调用f的结果相比,这显然是次优的。

错了

当内部Promise解析时,解析外部Promise。假设我们想从doTheThing * 返回 * 一个gResult的Promise:

function doTheThing() {
  return new Promise((resolve) => {
    f().then((fResult) => {
      g(fResult).then((gResult) => {
        resolve(gResult)
      })
    })
  })
}

为什么不好

这就是传说中的Promise constructor anti-pattern,除了Q&A中列出的问题之外,您不能再将错误处理推迟到调用者。

function doTheThing() {
  return f().then(g).catch(console.error)
}


如果你需要做更多的中间工作,而不仅仅是调用g并得到f的结果,你可以这样做:

function doTheThing() {
  return f()
    .then((fResult) => {
      // do intermediate work
      return g(fResult)
    })
    .catch(console.error)
}


这利用了Promises的自动扁平化:当我们从.then返回一个Promise时,我们得到的是Promise<T>而不是Promise<Promise<T>>。我们甚至可以添加另一个.then

function doTheThing() {
  return f()
    .then((fResult) => {
      // do intermediate work
      return g(fResult)
    })
    .then((gResult) => {
      // do something
    })
    .catch(console.error)
}


这种方法的另一个好处是,最后的单个.catch将捕获.then s链中任何地方的 * 所有 * Promise拒绝,如果您嵌套调用而不是链接它们,您不能这样做!

也对

async doTheThing() {
  const fResult = await f()
  const gResult = await g(fResult)
  // do stuff
  return gResult
}


很简单。完成任务。可以与try/catch结合使用,以轻松处理错误:

async doTheThing() {
  try {
    const fResult = await f()
    const gResult = await g(fResult)
    // do stuff
    return gResult
  } catch (err) {
    console.error(err)
  }
}


有关这些原则应用于代码的想法,请参阅this other answer that refactored your code

pqwbnv8z

pqwbnv8z3#

这有点难以发现,但问题的根源是use of the Promise constructor antipattern。当你试图处理所有的错误时,你犯了这样的错误

return reject({ status: 'error' })

字符串
完成mainFunc。但它并没有-它只从当前的.then()处理程序返回undefined,然后它以undefined作为参数执行下一个.then()处理程序,即使在你的promise已经被拒绝后也继续执行。
您可以通过在这里不链接.then(…).then(…)来修复错误,而是使用单个函数:

…
    return getAllRecords().then(getAllRecordsResponse => { 
      console.log("Called getAllRecords")
      if (getAllRecordsResponse.status !== 'success') { 
        return reject({ status: 'error' }) 
      } else {
//    ^^^^^^^^
        let updateArray = []
        console.log("Updating firestore db") 
        for (const city of cities) { 
          updateArray.push(firebaseDao.updatecityDoc(city)) 
        } 
        return Promise.allSettled(updateArray).then(…);
      }
    });
…


但是,这并不能大大改善您的代码。您的错误处理到处都是,有很多重复的代码,您应该摆脱new Promise。另外,如果你想使用.then()而不是await,没有理由将你的函数声明为async
对于错误处理,将错误保留为拒绝,并且只在最后将它们处理并转换为结果对象。

function getAllRecords(offset = 0) { 
  return fetchRecord(offset, 10).then(resp => { 
    const responseData = resp.data;
    if (responseData.length === 0 { 
      return;
    } 
    return proccessData(responseData).then(updateResp => {
      offset += 10;
      return getAllRecords(offset);
    });
  });
} 

function mainFunc() {
  console.log('Inside mainFunc') 
  return firestore.getAllCities().then(cities => { 
    return getAllRecords().then(getAllRecordsResponse => { 
      console.log("Called getAllRecords")
      const updateArray = []
      console.log("Updating firestore db") 
      for (const city of cities) { 
        updateArray.push(firebaseDao.updatecityDoc(city)) 
      } 
      return Promise.allSettled(updateArray);
    });
  }).then(responseArr => { 
    const errorArr = responseArr.filter(item =>
      item.status === 'rejected'
    ).map(item =>
      item.reason
    );
    if (!errorArr.length) {
      console.log("done processing");
      return { status: 'success' };
    } else { 
      return { status: 'error' };
    }
  }, err => { 
    return { status: 'error' };
  });
}


虽然这段代码已经简单得多了,但我建议使用async/await语法,如其他两个答案所示。您不再需要对getAllRecords使用递归,也不会有那么多嵌套,这大大简化了重构。

相关问题