如何在 Promise 之间异步传递数据?

Posted

技术标签:

【中文标题】如何在 Promise 之间异步传递数据?【英文标题】:How to pass data between promise asynchronously? 【发布时间】:2020-08-24 06:59:19 【问题描述】:

我想从后端获取几个数据,并在加载时处理它们。现在,当这段代码运行时,data1 和 data2 是未定义的。我想等他们,但我不知道如何保持我的代码干净。我不确定 Promise.all() 是否适合这里,因为我需要专门保存 data1 和 data2 的值,而且我无法编写通用代码来解析 Promise.all()。

return new Promise( (resolve,reject) => 
 let data1;
 let data2;

 let url1 = "http://fakeapi.com/getData1";
 fetch(url1)
 .then(res => res.json())
 .then(data => data1SpecificAction())
 .then(data => data1 = data)
 .catch("cannot fetch data1")

 let url2 = "http://fakeapi.com/getData2";
 fetch(url2)
 .then(res => res.json())
 .then(data => data2 = data)
 .catch("cannot fetch data2")

 if(data1 && data2) 
  resolve()
 
 else 
  reject()
 

我该如何解决这个问题?

【问题讨论】:

这个问题属于codereview.stackexchange.com 除了其他问题,你从来没有给data2分配任何东西……是错字吗? 对于初学者来说,任何时候你从.then()处理程序中为更高范围的变量赋值,你可能做错了什么。您将无法在需要时使用该更高范围的变量,因为仅在 .then() 处理程序内,因此您实际上知道它的值何时有效。 另外,我们可以更好地使用真实代码,而不是伪代码。 链接你的两个承诺或使用Promise.all()或使用await。这是你的三个选择。并且,摆脱您创建的new Promise()。当您已经从 fetch() 获得异步操作的承诺时,就不需要这样做了。你正在做的事情包含几个反模式。 【参考方案1】:

您的问题或答案中不需要很多内容。你可以这样做:

const url1 = "http://fakeapi.com/getData1";
const url2 = "http://fakeapi.com/getData2";
return Promise.all([
    fetch(url1).then(res => res.json()).then(data1SpecificAction), 
    fetch(url2).then(res => res.json())
]).then([data1, data2] => 
    if (data1 && data2) return;         // resolve
    // reject with appropriate error
    let msg = data1 ? "missing data2" : "missing data1";
    throw new Error(msg);
);

你的问题和答案中不需要做的事情:

    不要将现有的 Promise 包装在另一个手动创建的 Promise 中。这被认为是一种反模式,因为根本不需要手动创建的 Promise。你可以只返回你已经拥有的承诺。 不要将.then() 结果分配给更高范围的变量。虽然偶尔会出现这种情况,但这不是其中之一,通常是您做错事的警告信号。

我发现fetch() 接口有一些烦恼,我发现它们经常受益于辅助函数(例如 404 状态解决,而不是拒绝):

function fetchJson(...args) 
    return fetch(...args).then(res => 
        if (!res.ok) throw new Error(`Got $res.status status`);
        return res.json();
    );


const url1 = "http://fakeapi.com/getData1";
const url2 = "http://fakeapi.com/getData2";
return Promise.all([
    fetchJson(url1).then(data1SpecificAction), 
    fetchJson(url2)
]).then([data1, data2] => 
    if (data1 && data2) return;         // resolve
    // reject with appropriate error
    let msg = data1 ? "missing data2" : "missing data1";
    throw new Error(msg);
);

而且,在您想要拒绝结果错误的特定情况下,您甚至可以这样做:

function fetchJsonCheck(...args) 
    return fetch(...args).then(res => 
        if (!res.ok) throw new Error(`Got $res.status status`);
        return res.json();
    ).then(result => 
        if (!result) throw new Error("empty result");
        return result;
    );


const url1 = "http://fakeapi.com/getData1";
const url2 = "http://fakeapi.com/getData2";
return Promise.all([
    fetchJsonCheck(url1).then(data1SpecificAction), 
    fetchJsonCheck(url2)
]);

【讨论】:

@eelkejohnson - 您如何看待这些更简单的选项? 非常感谢!它真的帮助我度过了低谷。在我的真实情况下,我有 4 .then() + 1.catch() 用于第一个动作,2 .then() 和 1.catch() 用于第二个动作。这种方式更轻,因为您不需要将所有动作包装成更小的块。但就我而言,它确实提高了我的代码的可读性。这是我没有依赖的完整代码:pastebin.com/xPGwx0qg 总而言之,我了解了我正在编写的反模式,就我而言:1) 提高了可读性,2) 我不知道我可以像这样传递数据:) @EelkeJohnson - 仅供参考,您的 exports.controlInstantIncident 函数包含一个承诺反模式。你不需要将所有这些都包装在一个承诺中。只需返回studentService.getStudentFromHash(...).then(...).then(...).catch(...)。您在许多其他功能中重复该错误。您通过从最后一个.then() 返回的值来设置解析值。您通过从.catch() 抛出的内容设置拒绝原因。无需包装新的承诺。这是一个纯粹的 promise 反模式。返回您现有的承诺。不要用新的包装。 现在,我看到我在任何地方都像这样编写了我的 50k 行代码应用程序:O,并且我开始在只有 500 mb 内存的树莓派 3 型号 b 上运行我的应用程序。我广泛使用的这种反模式会导致浏览器出现内存问题吗?因为它产生了不必要的承诺?即使我的应用程序运行正常,我是否应该花时间修复所有后端? (我的时间很短)或者差异会非常明显?谢谢你的帮助【参考方案2】:

解决方案是将我的具体操作放入另一个承诺中并使用 promise.all

return new Promise( (resolve,reject) => 
 let data1;
 let data2;

 let promise1 = new Promise( (resolve,reject) => 
  let url1 = "http://fakeapi.com/getData1";
  fetch(url1)
  .then(res => res.json())
  .then(data => data1SpecificAction())
  .then(data =>  
   data1 = data;
   resolve()
   
  .catch(()=>reject("cannot fetch data1"))
 )

 let promise 2 = new Promise( (resolve,reject) => 
  let url2 = "http://fakeapi.com/getData2";
  fetch(url2)
  .then(res => res.json())
  .then(data =>  
   data2 = data;
   resolve()
   
  )
  .catch(()=>reject("cannot fetch data2"))
 )

 Promise.all([promise1,promise2])
 .then(
  () => 
   if(data1 && data2) 
    resolve()
   
   else 
     reject()
   
  
 )

here 是一个解释如何在 Promise 之间传递数据的链接。

【讨论】:

@trincot 这个怎么样? 我不明白那篇文章如何适用于您的情况。那篇文章是关于如何在属于同一链的 Promise 链之间共享数据 - 你有两个独立的 Promise 链。我提供了一个答案,这是我能看到的最简化的方法,可以让它工作并避免各种反模式。 这不是一个好的解决方案,因为您一直在使用 Promise 构造函数反模式。

以上是关于如何在 Promise 之间异步传递数据?的主要内容,如果未能解决你的问题,请参考以下文章

Promise对象和回调函数,解决异步数据传递问题

RxJS Promise 组合(传递数据)

jQuery中异步问题:数据传递

Promise的用法

如何将额外数据传递到 Parse Promise 链 [重复]

学习promise