使用1个解析和1个拒绝链接异步Promises

Chaining async Promises with 1 resolve and 1 reject?

本文关键字:1个 链接 异步 Promises 拒绝 使用      更新时间:2023-09-26

我有一个函数,它必须在几个步骤中执行异步操作。每走一步都可能失败。它可能在步骤1之前失败,因此您可能会立即或1.5秒后知道结果。失败时,它必须运行回调。当成功时Idem。(我故意在时使用,因为不仅仅是:时间很重要。)

我认为Promises是完美的,因为async和它们只解决一次,但它仍然有一个问题:它什么时候失败?我可以清楚地看到它什么时候成功(在最后一步之后),但它什么时候失败?内部/任何步骤之前。

这就是我现在所拥有的,但这太荒谬了:

function clickSpeed() {
    return new Promise(function(resolve, reject) {
        if ( test('one') ) {
            return setTimeout(function() {
                if ( test('two') ) {
                    return setTimeout(function() {
                        if ( test('three') ) {
                            return setTimeout(function() {
                                console.log('resolving...');
                                resolve();
                            }, 500);
                        }
                        console.log('rejecting...');
                        reject();
                    }, 500);
                }
                console.log('rejecting...');
                reject();
            }, 500);
        }
        console.log('rejecting...');
        reject();
    });
}

test()随机通过或失败一步。)

Fiddle here:http://jsfiddle.net/rudiedirkx/zhdrjjx1/

我猜解决方案是连锁承诺,它解决或拒绝每一步。。?大概这是一件事吗?我将如何实现?

这能在未知数量的步骤中工作吗?

您可以将您的解决方案重写为字面上的承诺:

function sleep(ms) {
    return new Promise(function(resolve) {
        setTimeout(resolve, ms);
    });
}
function clickSpeed() {
    if ( test('one') ) {
        return sleep(500).then(function() {
            if ( test('two') ) {
                return sleep(500).then(function() {
                    if ( test('three') ) {
                        return sleep(500).then(function() {
                            console.log('resolving...');
                        });
                    }
                    console.log('rejecting...');
                    return Promise.reject();
                });
            }
            console.log('rejecting...');
            return Promise.reject();
        });
    }
    console.log('rejecting...');
    return Promise.reject();
}

然而,这仍然很丑陋。我宁愿将if/else反转为第一步:

function clickSpeed() {
    if (!test('one')) {
        console.log('rejecting...');
        return Promise.reject();
    }
    return sleep(500).then(function() {
        if (!test('two')) {
            console.log('rejecting...');
            return Promise.reject();
        }
        return sleep(500).then(function() {
            if (!test('three')) {
                console.log('rejecting...');
                return Promise.reject();
            }
            return sleep(500).then(function() {
                console.log('resolving...');
            });
        });
    });
}

但是我们也可以取消这些回调。当您使用if进行分支时,这通常是不可能的,但在这种情况下,唯一的替代结果是拒绝,这与throwing类似,并且不会执行链接的then回调。

function clickSpeed() {
    return Promise.resolve() // only so that the callbacks look alike, and to use throw
    .then(function() {
        if (!test('one')) {
            console.log('rejecting...');
            throw;
        }
        return sleep(500);
    }).then(function() {
        if (!test('two')) {
            console.log('rejecting...');
            throw;
        }
        return sleep(500)
    }).then(function() {
        if (!test('three')) {
            console.log('rejecting...');
            throw;
        }
        return sleep(500);
    }).then(function() {
        console.log('resolving...');
    });
}

现在,您可以让这些test本身抛出异常,这样您就不再需要if了,并且可以在.catch()中移动console.log('rejecting...');语句。

虽然我想这只是你的例子,所有步骤看起来都一样,但你也可以很容易地从列表中动态创建承诺链:

function clickSpeed() {
    return ['one', 'two', 'three'].reduce(function(p, cur) {
        return p.then(function() {
            if (!test(cur))
                throw new Error('rejecting...');
            else
                return sleep(500);
        });
    }, Promise.resolve());
}

由于您的时间安排是单调的,并且您的"工作"是预定义的,因此我会重构代码,以使用带有上下文的setInterval()来跟踪所需的"工作量"或"步骤"。事实上,在这样做的时候,你甚至没有使用Promise,尽管你仍然可以,如果你想进一步使用链处理程序,这是一个好主意:

function clickSpeed(resolve, reject) {
  var interval = setInterval(function(work) {
    try {
      var current = work.shift();
      if(!test(current)) { // Do current step's "work"
        clearInterval(interval); // reject on failure and clear interval
        console.log('rejecting...', current);
        reject();
      }
      else if(!work.length) { // If this was the last step
        clearInterval(interval); // resolve (success!) and clear interval
        console.log('resolving...');
        resolve();
      }
    }
    catch(ex) { // reject on exceptions as well
      reject(ex);
      clearInterval(interval);
    }
  }, 500, ['one', 'two', 'three']); // "work" array
}

该函数可以用resoly/reject处理程序"直接"调用,也可以用作Promise构造函数的参数。

请参阅修改后的JSFiddle中的完整示例。


为了解决Bergi的太多样板注释,可以更简洁地编写代码,而无需记录:

function clickSpeed(resolve, reject) {
    function done(success, val) {
        clearInterval(interval);
        success ? resolve(val) : reject(val);
    }
    var interval = setInterval(function(work) {
        try {
            if(test(work.shift()) || done(false)) {
                work.length || done(true);
            }
        }
        catch(ex) { // reject on exceptions as well
            done(false, ex);
        }
    }, 500, ['one', 'two', 'three']); // "work" array
}

首先,让我们非常巧妙地重新排列开场白,以反映更典型、更恰当地使用promise。

代替:

一个必须执行异步操作的函数,只需几个步骤

比方说:

一个必须在几个异步步骤中执行操作的函数

因此,我们可能首先选择编写一个函数来执行承诺链并返回其结果承诺:

function doAnAsyncSequence() {
    return Promise.resolve()
    .then(function() {
        doSomethingAsync('one');
    })
    .then(function() {
        doSomethingAsync('two');
    })
    .then(function() {
        doSomethingAsync('three');
    });
}

而且,为了演示起见,我们可以编写doSomethingAsync(),使其返回一个有50:50机会被解决的承诺:拒绝(在这里比延迟更有用):

function doSomethingAsync(x) {
    return new Promise(function(resolve, reject) {
        if(Math.random() > 0.5 ) {
            resolve(x);
        } else {
            reject(x); // importantly, this statement reports the input argument `x` as the reason for failure, which can be read and acted on where doSomethingAsync() is called.
        }
    });
}

然后,问题的核心部分:

它什么时候会失败?

可以改写为:

它什么时候失败的?

这是一个更现实的问题,因为我们通常会调用异步进程,而我们对这些进程几乎没有影响(它们可能在世界其他地方的某个服务器上运行),我们希望它会成功,但可能会随机失败。如果是这样,我们的代码(和/或最终用户)希望知道哪一个失败了,以及为什么。

doAnAsyncSequence()的情况下,我们可以这样做:

doAnAsyncSequence().then(function(result) {
    console.log(result); // if this line executes, it will always log "three", the result of the *last* step in the async sequence.
}, function(reason) {
    console.log('error: ' + reason);
});

尽管在doAnAsyncSequence()doSomethingAsync():中没有console.log()语句

  • 关于成功,我们可以观察到总体结果(在本例中总是"三")
  • 在出现错误时,我们确切地知道是哪个异步步骤导致了进程失败("一个"、"两个"或"三个")

试试


这就是理论。

要回答具体的问题(据我所知)。。。

对于doSomethingAsync(),写入:

function test_(value, delay) {
    return new Promise(function(resolve, reject) {
        //call your own test() function immediately
        var result = test(value);
        // resolve/reject the returned promise after a delay
        setTimeout(function() {
            result ? resolve() : reject(value);
        }, delay);
    });
}

对于doAnAsyncSequence(),写入:

function clickSpeed() {
    var delayAfterTest = 500;
    return Promise.resolve()
    .then(function() {
        test_('one', delayAfterTest);
    })
    .then(function() {
        test_('two', delayAfterTest);
    })
    .then(function() {
        test_('three', delayAfterTest);
    });
}

并按如下方式调用:

clickSpeed().then(function() {
    console.log('all tests passed');
}, function(reason) {
    console.log('test sequence failed at: ' + reason);
});