【问题标题】:throw error in catch block Q promise not rejecting the promise在 catch 块中抛出错误 Q 承诺不拒绝承诺
【发布时间】:2016-12-12 01:31:48
【问题描述】:

我有以下函数被另一个函数调用:

 function fetchAllRecords(client, item, region, offset, savedCount, totalCount) {
  offset = offset || 1;
  savedCount = savedCount || 0;
  totalCount = totalCount || 0;
  return processQueue(client, item, offset, region).then(function (result) {
    return associate(result, item, region)
  }).then(function (success) {
    return saveBatch(item, success.allResources, region);
  }).then(function (result) {
    savedCount += result.savedCount;
    totalCount += result.totalCount;
    if (debugmode) {
      console.log(savedCount + '/' + totalCount);
    }
    offset += item.limit;
    return fetchAllRecords(client, item, region, offset, savedCount, totalCount);
  }).catch(function (err) {
    if (err == 'done')
      return 'done';

    // All of the above steps have built-in retry so we assume there's a non-recoverable error in this batch and move to next
    if (err.message === 'LIMIT_EXCEEDED') {
      offset += item.limit;
      return fetchAllRecords(client, item, region, offset, savedCount, totalCount);
    }
    else {
******************* Problem Line ********************
      console.log('Miscellenious error in fetachAllRecords, moving to next resource');
      console.log(JSON.stringify(err));
      throw new Error('Misc');
    }
  });
}

它在另一个函数中被这样调用

function processResource(client, item, debugmode, region) {
  var deferred = q.defer();
  if (item.resource === "Property" && region === "ccmls") {
    var cities = cities.list;
    item.query = item.query + ' ,(City=|' + cities.join() + ')';
  }
  fetchAllRecords(client, item, region)
    .then(function (result) {
      if (debugmode) {
        console.log('fetchAllRecords: ' + item.resource + '.' + item.class + ' completed...');
      }
      deferred.resolve(result);
    })
    .catch(function (err) {
      console.log(item.resource + '.' + item.class + ' failed with error: ' + JSON.stringify(err));
      deferred.reject(err);
    });
  return deferred.promise;
}

在上面的问题行中,它应该拒绝fetchAllRecords,而在processResource 中,应该调用fetachAllResources catch 处理程序,但是由于某种奇怪的原因,问题行在 throw 之后和之后一直被调用被调用了十几次(随机),它最终拒绝了fetchAllResourcesprocessResource 中返回的承诺。 我在这里遗漏了一些明显的东西吗?另外请评论一下我使用 Promise 的风格,可以吗?还是我需要更多练习?

【问题讨论】:

标签: javascript promise q


【解决方案1】:

您会获得大量日志,因为您使用递归方法,并在每个级别处理和重新抛出错误。同步写的,一模一样

function fetchAll(offset) {
    if (offset > 5) throw new Error("message"); // let's say the inner one throws
    try {
        return fetchAll(offset+1);
    } catch(e) {
        console.error(e.message);
        throw e;
    }
}
fetchAll(0);

您应该也会在这里收到 5 条消息,对吧?

解决方法是不再处理内部结果中的错误。要通过 Promise 实现这一点,请查看 difference between .then(…).catch(…) and .then(…, …) - 你想要后者:

function fetchAllRecords(client, item, region, offset=1, savedCount=0, totalCount=0) {
  return processQueue(client, item, offset, region).then(function (result) {
    return associate(result, item, region)
  }).then(function (success) {
    return saveBatch(item, success.allResources, region);
  }).then(function (result) {
    savedCount += result.savedCount;
    totalCount += result.totalCount;
    if (debugmode) {
      console.log(savedCount + '/' + totalCount);
    }
    offset += item.limit;
    return fetchAllRecords(client, item, region, offset, savedCount, totalCount);
  }, function (err) {
// ^^
    if (err == 'done')
      return 'done';

    if (err.message === 'LIMIT_EXCEEDED') {
      offset += item.limit;
      return fetchAllRecords(client, item, region, offset, savedCount, totalCount);
    } else {
      console.log('Miscellenious error in fetchAllRecords, moving to next resource');
      console.log(JSON.stringify(err));
      throw new Error('Misc');
    }
  });
}
function processResource(client, item, debugmode, region) {
  if (item.resource === "Property" && region === "ccmls") {
    var cities = cities.list;
    item.query = item.query + ' ,(City=|' + cities.join() + ')';
  }
  return fetchAllRecords(client, item, region)
  .then(function (result) {
    if (debugmode) {
      console.log('fetchAllRecords: ' + item.resource + '.' + item.class + ' completed...');
    }
    return result;
  }, function (err) {
    console.log(item.resource + '.' + item.class + ' failed with error: ' + JSON.stringify(err));
    throw err;
  });
}

【讨论】:

    【解决方案2】:

    我认为您收到的错误很可能是在您对堆栈上的方法进行了十几个调用之后发生的。

    即,假设您在调用 processQueue 方法时有以下场景:

    成功,成功,成功,其他失败

    现在,请注意我在下面的代码中标记的行。 (我将这些行称为,例如 LINE A2,它将在 fetchAllRecords 的第二次调用中引用 LINE A):

    function fetchAllRecords(client, item, region, offset, savedCount, totalCount) {
      offset = offset || 1;
      savedCount = savedCount || 0;
      totalCount = totalCount || 0;
    /*********************** LINE A **************************/
      return processQueue(client, item, offset, region).then(function (result) {
        return associate(result, item, region)
      }).then(function (success) {
        return saveBatch(item, success.allResources, region);
      }).then(function (result) {
        savedCount += result.savedCount;
        totalCount += result.totalCount;
        if (debugmode) {
          console.log(savedCount + '/' + totalCount);
        }
        offset += item.limit;
    /*********************** LINE B **************************/
        return fetchAllRecords(client, item, region, offset, savedCount, totalCount);
      }).catch(function (err) {
        if (err == 'done')
          return 'done';
    
        // All of the above steps have built-in retry so we assume there's a non-recoverable error in this batch and move to next
        if (err.message === 'LIMIT_EXCEEDED') {
          offset += item.limit;
          return fetchAllRecords(client, item, region, offset, savedCount, totalCount);
        }
        else {
    /*********************** LINE C **************************/
          console.log('Miscellenious error in fetachAllRecords, moving to next resource');
          console.log(JSON.stringify(err));
    /*********************** LINE D **************************/
          throw new Error('Misc');
        }
      });
    }
    

    当我们进入时会发生什么:

    • LINE A1 // 成功完成,然后继续
    • B1 线
    • LINE A2 // 成功完成,然后继续
    • B2 线
    • LINE A3 // 成功完成,然后继续
    • B3 线
    • LINE A4 // 错误...异常冒泡
    • LINE B3 // 异常正在冒泡...
    • Q 服务 -> 在 then 块中检测到异常,包含 B3 行,重定向到 catch... 假设这是一个杂项错误
    • C3线
    • 第 D3 行 // 错误...
    • Q 服务 -> 在包含行 D3 的 catch 块中检测到异常... 也就是说,在 B2 行上创建的 Promise 被拒绝。 但是当这个 promise 被返回时,它就构成了方法 2 中线性 promise 链的一部分。因此这个拒绝被传播到了 catch 上......所以我们接下来点击:
    • C2线
    • 第 D2 行 // 错误...
    • 重复。
    • C1 线
    • 第 D1 行 // 错误...
    • Q 服务将拒绝传播回 processResource,然后到达那里的 catch 块。

    这导致 D 行被调用的次数比进程资源中的 catch 块多得多。

    希望这是有道理的。

    RE 你对承诺的使用——你在 processResource 方法中使用了一个反模式(参考:https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern)——你应该很少需要自己创建延迟,相反,你可以依赖 then 和 catch 的链接行为(参见https://github.com/kriskowal/q)。也就是说,你可以这样写:

    function processResource(client, item, debugmode, region) {
      if (item.resource === "Property" && region === "ccmls") {
        var cities = cities.list;
        item.query = item.query + ' ,(City=|' + cities.join() + ')';
      }
      return fetchAllRecords(client, item, region)
        .then(function (result) {
          if (debugmode) {
            console.log('fetchAllRecords: ' + item.resource + '.' + item.class + ' completed...');
          }
          return result;
        })
        .catch(function (err) {
          console.log(item.resource + '.' + item.class + ' failed with error: ' + JSON.stringify(err));
          return Q.reject(err);
        });
    }
    

    一般来说,如果您有编译器的能力,我建议您使用类似 babel(或 typescript)的东西 - 这意味着您可以使用 ES6 箭头函数表示法编写,这可以使 Promise 更具可读性.

    【讨论】:

    • 没问题。感谢您发现问题,我已经发布了避免多次记录的解决方案
    • 我喜欢你的解释 - 你在那里提出的建议很好解决
    猜你喜欢
    • 1970-01-01
    • 2017-06-04
    • 2020-04-01
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    • 2019-08-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多