繁体   English   中英

用1个决心和1个拒绝来链接异步Promises?

[英]Chaining async Promises with 1 resolve and 1 reject?

我有一个必须在几步中完成异步操作的函数。 在每个步骤中它都可能失败。 它可能在步骤1之前失败,因此您可能立即或在1.5秒后知道结果。 如果失败,则必须运行回调。 同上, 成功。 (我故意的时候 ,使用,因为它不只是 :时机很重要)

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

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

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()随机通过或失败了一个步骤。)

在这里提琴: 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进行分支时,通常是不可能的,但是在这种情况下,唯一的替代结果是拒绝,这就像throw ,并且不会执行链接的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任何更多的,你可以移动console.log('rejecting...'); .catch()语句。

虽然我猜这只是所有步骤看起来都一样的示例,但是您也可以轻松地从列表中动态创建承诺链:

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());
}

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

代替 :

只需几步即可完成异步功能的功能

比方说:

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

因此,我们可能首先选择编写一个函数,该函数执行一个Promise链并返回其最终的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);
});

由于您的时间安排是单调的,并且您的“工作”是预定义的,因此我将重构代码以利用setInterval()和上下文来跟踪所需的“工作”或“步骤”。 实际上,尽管您仍然可以使用Promise,但您甚至不必使用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
}

该函数可以使用“解析/拒绝”处理程序“直接”调用,也可以用作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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM