如何同步 Promise 对象?

Posted

技术标签:

【中文标题】如何同步 Promise 对象?【英文标题】:How To Synchronise Promise Objects? 【发布时间】:2017-01-05 21:14:46 【问题描述】:

我有需要同步工作的承诺对象。例如,在第一个承诺完成之前,第二个承诺不应该起作用。如果第一个拒绝第一个必须再次执行。

我已经实现了一些示例。这个效果很好。 调用getVal,等待2000ms,返回,i++,再次调用getVal.....

 getVal() 
       return new Promise(function(resolve, reject) 
      setTimeout(function() resolve(19) , 2000);
         );

     

async promiseController()

    for(var i =0;i<5;i++)
      
        var _val = await this.getVal()
        console.log(_val+'prom');
      
    

但我需要控制一组 promise 对象。我想要做的是我有一个数据,我把它分成了5块。处理好第一部分(例如:发送到服务器)后,我想处理第二部分,否则我必须再次处理第一部分。

这是我做的原型实现

  getVal() 
   return new Promise(function(resolve, reject) 
  setTimeout(function() resolve(19) , 2000);
     );

 

async promiseController()
  var proms=[]
  for(var i =0;i<5;i++)
    
      proms.push(this.getVal())
    

for(var i =0;i<5;i++)
  
    var _val = await proms[i]
    console.log(_val+'prom');
  

此代码中的 Promises 对象按顺序工作。如何修复下面的代码,使其作为第一个示例同步工作。

【问题讨论】:

我不知道您是在尝试做 Promise.all(),还是希望您的承诺做更多的瀑布类型的事情。 promise.all() 对我不起作用。它在一个承诺失败后拒绝这条线。在我的情况下,我必须让它们全部工作,否则它是没用的。 如果返回第一个promise的递归调用没有解决,预期的结果是什么? 是什么阻止了第一个承诺无限期地重新运行? @guest71314 var i in for 将被减少,第一个 promise 将再次被触发 【参考方案1】:
async promiseController()
  for(const value of array) 
    console.log((await this.getVal(value))+'prom');
  

无需使事情过于复杂。只需在循环内调用await,它就会等待你想要的。

正如另一个答案正确地说 - 一个承诺代表一个价值,而不是一个操作。对于操作,使用常规函数。

如果你想忽略失败,你可以.catch(() =&gt; ) 承诺。如果您想重试直到失败 - 您可以将重试重构为一个函数并使用它:

const retry = fn => (...args) => fn(...args).catch(retry(fn));

【讨论】:

谢谢,但正如我在问题中提到的,我有 promise array 。调用 getVal 对我不起作用 这没什么区别,而不是从 0 迭代到 4 迭代值。我已经更新了示例以显示它而不是 0..4 你的意思是 (await this.getVal(value)) 或 (await value) await this.getVal(value) - 保留一个值数组并在循环中处理它们。一旦你有了承诺,你就已经失去了同步它们的机会。你同步函数。 '一旦你有承诺,你已经失去了同步它们的机会'这是关键。谢谢,但我不明白如何使用这一行 const retry = fn => (.. .args) => fn(...args).catch(retry(fn));【参考方案2】:

如果您的目标是在第一个 Promise 解决之前不“执行后续的 Promise”,那么您需要记住,Promise 代表异步活动已经在进行中。一旦承诺存在,就为时已晚。

您需要在第一个 Promise 完成之前不要调用后续的 Promise 工厂方法。您的第一个示例通过在前一个承诺完成之前不调用 getVal() 来做到这一点。

所以你最终会得到类似的东西:

delay(time) 
    return new Promise(resolve => setTimeout(resolve, time));


async promiseController() 
    const factories = [];
    for (let i = 0; i < 5; ++i) 
        factories.push(() => this.getVal());
    

    for (const f of factories) 
        // keep running this factory until it succeeds
        let success = false;
        while (!success) 
            try 
                const promise = f();
                const result = await f;
                success = true;
                console.log(`result = $result`);
            
            catch (err) 
                console.log("promise failed.  retrying");
                await delay(100);
            
        
    

【讨论】:

谢谢。您是在说“您的第一个示例通过在前一个承诺完成之前不调用 getVal() 来做到这一点。”第二个例子有什么问题。 await this.getVal() 和 await proms[i] 有什么区别。两者都有一个承诺,不是吗?【参考方案3】:

可以使用递归,命名函数,.then()

var arr = [Promise.resolve("a")
           , Promise.resolve("b")
           , Promise.resolve("c")];
var i = 0;
var res = [];

function foo() 
  // conditional resolved or rejected promise
  var n = String(new Date().getTime()).slice(-1);
  // if `n` < 5 reject `n` , else resolve `n`
  var curr = n < 5;
  return curr ? arr[i] : Promise.reject(["rejected", n])


var p = (function repeat() 
  var promise = foo();
  return promise
    .then(function(data) 
      console.log(data);
      res.push(data);
      ++i;
      if (i < arr.length) return repeat()
      // return `res` array when all promises complete
      else return res
    )
    .catch(function(err) 
      console.log(err);
      if (err[0] === "rejected") return repeat()
    )
());

p.then(function(complete) 
  console.log("complete:", complete)
);

【讨论】:

【参考方案4】:

好吧。我相信为了正确的函数式编程目的,应该避免使用asyncawait。我相信承诺是非常充分的。但是,如果您想继续以 C++ 类似的命令式风格进行编码,那么 asyncawait 适合您。

我有需要同步工作的承诺对象。例如 在第一个承诺完成之前,第二个承诺不应该起作用。如果第一个 拒绝第一个必须再次执行。

让我简要介绍一下下面的代码。我们有 async() 函数,它接受数据和回调(错误第一类型)。至于演示目的,它将尝试在 2000 毫秒内使用数据调用回调,但是在 1000 毫秒时会出现超时。所以 50-50 它将调用带有数据或错误的回调。

所以我们实际上需要它来向我们返回一个承诺,所以我在 promisify() 的帮助下承诺它,它需要 async() 函数并返回 asyncPro() 函数。这实际上与async() 相同,但返回的是一个承诺。所以我们应该在then 阶段使用我们的回调。

然后是tryNTimes(data,asyncFun,n = 5) 函数,它接收数据、一个承诺的异步函数和一个整数,指定在拒绝之前尝试的次数。默认尝试计数为 5,但您可以通过传递第三个参数将其设置为任何值。

至于最后一部分,我们有flowControl(),它在Array.prototype.reduce() 的帮助下完美地链接了我们的承诺。

所以现在我们将所有的承诺一个接一个地链接起来,在尝试 5 次之前没有一个会失败。

function promisify(fun)
  return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));


function async(data, callback)
  var dur = Math.floor(Math.random()*2000);
  setTimeout(_ => callback(false,data),dur);           // may resolve before timeout
  setTimeout(_ => callback("error at " + data),1000);  // timeout at 1 sec


function tryNTimes(data,asyncFun,n = 5)
  return new Promise((resolve,reject) =>  n === 0 && reject("try out fail at 5 tries: " + data);
                                           asyncFun(data).then(v => resolve("resolved at countdown " + n + ": " + v))
                                                         .catch(e => resolve(tryNTimes(data,asyncFun,--n)));
                                         );


function flowControl(d,f,tc)
  return d.reduce((prom,chunk) => prom.then(v =>  console.log(v);
                                                   return tryNTimes(chunk,f,tc);
                                                 ),Promise.resolve("initial dummy promise"));


var data = ["chunk_1", "chunk_2", "chunk_3", "chunk_4", "chunk_5"],
asyncPro = promisify(async);                           // now our async function returns a promise

flowControl(data,asyncPro).then(v => console.log(v))
                          .catch(e => console.log(e));

如果您想更频繁地看到“5 次尝试”错误,请降低async() 函数中的超时值。

【讨论】:

感谢这个详细和解释性的美丽示例。一步没看懂。 Promise 有一个执行器函数,这个函数会立即执行它说 (developer.mozilla.org/en-US/docs/Web/javascript/Reference/…) 你让这个函数异步了对吗?如何使这个函数异步导致该函数不被立即调用? @Burak Karasoy 不是async。是的,当你生成一个promise对象时,你让它运行一个执行器函数,在我们的例子中,执行器在promisfy函数中。(resolve,reject) =&gt; fun(data, (err,res) =&gt; err ? reject(err) : resolve(res))它将同步执行并返回一个promise对象,它将等待我们的async功能解决或拒绝。如果你看 asyncPro = promisify(async); 指令使我们的async 函数成为promisfy 中的fun 参数,我们正在为async 函数提供我们执行者的resolvereject 回调 promisify() 不返回一个承诺,它返回一个返回一个承诺的函数,这就是为什么不立即触发承诺,而是在方法被调用之后才触发的原因?在这种情况下,方法的名称是否必须是异步的?或者你只是因为它有 setTimeout(..) 而称它为 async 你不能将它命名为 _asyncFunc 而不是 async 好的,我试过了,得到了答案。由于 async 是 ecmascript 中的一个关键字,这让我很困惑。我认为它类似于 async-await。(ponyfoo.com/articles/understanding-javascript-async-await) @Burak Karasoy 是的,是的。promisfy 使一个普通的回调样式异步函数返回一个承诺对象。它返回我们async 函数的承诺版本。是的,我们的执行器功能嵌入在asyncPro 中。我只是称它为async 只是因为那一刻我找不到更好的名字。完全按照惯例,没什么特别的。如果您愿意,可以将其命名为 kediCesur。 :) 很抱歉让您混淆了这个名字。

以上是关于如何同步 Promise 对象?的主要内容,如果未能解决你的问题,请参考以下文章

Promise对象

JS中promise对象的作用与使用

浅谈promise对象

Promise基本使用

javascript:Promise 对象

Promise对象解读