Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/maven/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript “更好的方法”;“循环”;承诺_Javascript_Recursion_Ecmascript 6_Promise - Fatal编程技术网

Javascript “更好的方法”;“循环”;承诺

Javascript “更好的方法”;“循环”;承诺,javascript,recursion,ecmascript-6,promise,Javascript,Recursion,Ecmascript 6,Promise,这篇文章可能会给人一种相当概念化的印象,因为我第一次开始时有很多伪代码。-最后,您将看到这个问题的用例,尽管解决方案是“我可以添加到有用编程技术的工具带中的工具” 问题 有时,一个人可能需要创建多个承诺,或者在所有承诺都结束后做一些事情。或者,可以根据先前承诺的结果创建多个承诺。类似于创建一个值数组而不是单个值。 有两种基本情况需要考虑,一种是承诺的数量与所述承诺的结果无关,另一种是依赖于承诺的情况。简单的伪代码“可以”做什么 这要复杂得多,因为你不能只是开始所有的承诺,然后等待它们完成:最终你

这篇文章可能会给人一种相当概念化的印象,因为我第一次开始时有很多伪代码。-最后,您将看到这个问题的用例,尽管解决方案是“我可以添加到有用编程技术的工具带中的工具”

问题 有时,一个人可能需要创建多个承诺,或者在所有承诺都结束后做一些事情。或者,可以根据先前承诺的结果创建多个承诺。类似于创建一个值数组而不是单个值。
有两种基本情况需要考虑,一种是承诺的数量与所述承诺的结果无关,另一种是依赖于承诺的情况。简单的伪代码“可以”做什么

这要复杂得多,因为你不能只是开始所有的承诺,然后等待它们完成:最终你必须“一个承诺一个承诺”。这第二种情况是我想弄明白的(如果一个人可以做第二种情况,你也可以做第一种情况)

(坏的)解决办法 我确实有一个有效的“解决方案”。这不是一个很好的解决方案,可能很快就会看到,在代码之后,我将讨论我为什么不喜欢这种方法。基本上,它不使用循环,而是使用递归-因此“承诺”(或围绕承诺的包装器,即承诺)在实现时在代码中调用自己:

function promiseFunction(state_obj) {
    return new Promise((resolve, reject) => {
        //initialize fields here
        let InnerFn = (stateObj) => {
            if (!stateObj.checkContinue()) {
                return resolve(state_obj);
            }
            ActualPromise(...)
                .then(new function(result) {
                    newState = stateObj.cloneMe(); //we'll have to clone to prevent asynchronous write problems
                    newState.changeStateBasedOnResult(result);
                    return InnerFn(newState);
                })
                .catch(new function(err) {
                    return reject(err); //forward error handling (must be done manually?)
                });
        }
        InnerFn(initialState); //kickstart
    });
}
需要注意的重要一点是,
stateObj
在其生命周期内不应更改,但它可以非常简单。在我真正的问题(我将在最后解释)中,stateObj只是一个计数器(数字),而
if(!stateObj.checkContinue())
只是
if(计数器

现在这个解决方案真的很糟糕;它丑陋、复杂、容易出错,最终无法扩展。
丑陋是因为实际的业务逻辑隐藏在混乱的代码中。它不会显示“在can上”实际上只是执行上面while循环所做的操作。
复杂是因为执行流程无法遵循。首先,递归代码从来都不容易理解,但更重要的是,您还必须记住状态对象的线程安全性。(也可能有对另一个对象的引用,例如,存储一个结果列表以供以后处理)。
它容易出错,因为冗余比严格要求的要多;您必须明确地转发拒绝。调试工具(如堆栈跟踪)也很快变得很难查看。
在某些情况下,可伸缩性也是一个问题:这是一个递归函数,因此在某一点上,它将创建一个stackoverflow/遇到最大递归深度。通常,可以通过尾部递归进行优化,或者更常见的是,创建一个虚拟堆栈(在堆上),并使用手动堆栈将函数转换为循环。然而,在这种情况下,不能将递归调用更改为具有手动堆栈的循环;这仅仅是因为promise语法是如何工作的

替代(坏)解决方案 一位同事提出了解决这个问题的另一种方法,这种方法最初看起来问题不大,但我最终放弃了,因为它违背了承诺要做的一切

他建议的基本上是按照上面的承诺循环。但不是让循环继续,而是有一个变量“finished”和一个内部循环不断地检查这个变量;因此,在代码中,它将类似于:

function promiseFunction(state_obj) {
    return new Promise((resolve, reject) => {
        while (stateObj.checkContinue())  {
            let finished = false;
            let err = false;
            let res = null;
            actualPromise(...)
                .then(new function(result) {
                    res = result;
                    finished = true;
                })
                .catch(new function(err) {
                    res = err;
                    err = true;
                    finished = true;
                });
            while(!finished) {
                sleep(100); //to not burn our cpu
            }
            if (err) {
                return reject(err);
            }
            stateObj.changeStateBasedOnResult(result);
        }
    });
}
虽然这不那么复杂,因为现在很容易遵循执行流程。这本身就有问题:至少这个功能何时结束还不清楚;这对性能非常不利

结论 这还不算什么,我真的想要像上面第一个伪代码一样简单的东西。也许是另一种看待事物的方式,这样人们就不会有深层递归函数的麻烦

那么,如何重写循环中的承诺呢

作为动机的真正问题 现在这个问题的根源在于我必须创造的一个真实的东西。虽然这个问题现在已经解决了(通过应用上面的递归方法),但了解是什么导致了这个问题可能会很有趣;然而,真正的问题不是关于这个具体案例,而是关于如何在有任何承诺的情况下做到这一点

在一个sails应用程序中,我必须检查一个数据库,其中有订单ID的订单。我必须找到前N个“不存在的订单ID”。我的解决方案是从数据库中获取“first”M产品,找出其中缺少的数字。然后,如果缺失数量小于N,则获得下一批M产品

现在要从数据库中获取一个项目,需要使用承诺(或回调),因此代码不会等待数据库数据返回。-所以我基本上处于“第二个问题”:

函数GenerateEmptySpots(maxNum){
返回新承诺((解决、拒绝)=>{
//初始化字段
让InnerFn=(计数器,r)=>{
如果(r>0){
返回resolve(true);
}
让查询={
orderNr:{'>=':计数器,'
替代(坏)解决方案

…实际上不起作用,因为JavaScript中没有
sleep
函数。(如果您有一个提供非阻塞睡眠的运行时库,您可以使用
,而
循环和非阻塞使用相同的样式在其中等待承诺)

糟糕的解决方案丑陋、复杂、容易出错,最终无法扩展

不,递归方法确实是正确的方法

丑陋是因为实际的业务逻辑隐藏在一堆乱七八糟的代码中,而且容易出错,因为您必须显式地转发拒绝

这仅仅是由…引起的!避免它

复杂是因为执行流程不可能遵循。递归代码从来都不“容易”遵循

我将对这一说法提出质疑。
function promiseFunction(state_obj) {
    return new Promise((resolve, reject) => {
        //initialize fields here
        let InnerFn = (stateObj) => {
            if (!stateObj.checkContinue()) {
                return resolve(state_obj);
            }
            ActualPromise(...)
                .then(new function(result) {
                    newState = stateObj.cloneMe(); //we'll have to clone to prevent asynchronous write problems
                    newState.changeStateBasedOnResult(result);
                    return InnerFn(newState);
                })
                .catch(new function(err) {
                    return reject(err); //forward error handling (must be done manually?)
                });
        }
        InnerFn(initialState); //kickstart
    });
}
function promiseFunction(state_obj) {
    return new Promise((resolve, reject) => {
        while (stateObj.checkContinue())  {
            let finished = false;
            let err = false;
            let res = null;
            actualPromise(...)
                .then(new function(result) {
                    res = result;
                    finished = true;
                })
                .catch(new function(err) {
                    res = err;
                    err = true;
                    finished = true;
                });
            while(!finished) {
                sleep(100); //to not burn our cpu
            }
            if (err) {
                return reject(err);
            }
            stateObj.changeStateBasedOnResult(result);
        }
    });
}
function GenerateEmptySpots(maxNum) {
    return new Promise((resolve, reject) => {
        //initialize fields
        let InnerFn = (counter, r) => {
            if (r > 0) {
                return resolve(true);
            }
            let query = {
                orderNr: {'>=': counter, '<': (counter + maxNum)}
            };
            Order.find({
                where: query,
                sort: 'orderNr ASC'})
                .then(new function(result) {
                    n = findNumberOfMissingSpotsAndStoreThemInThis();
                    return InnerFn(newState, r - n);
                }.bind(this))
                .catch(new function(err) {
                    return reject(err); 
                });
        }
        InnerFn(maxNum); //kickstart
    });
}
function promiseFunction(state) {
    const initialState = state.cloneMe(); // clone once for this run
    // initialize fields here
    return (function recurse(localState) {
        if (!localState.checkContinue())
            return Promise.resolve(localState);
        else
            return actualPromise(…).then(result =>
                recurse(localState.changeStateBasedOnResult(result))
            );
    }(initialState)); // kickstart
}
async function promiseFunction(state) {
    const localState = state.cloneMe(); // clone once for this run
    // initialize fields here
    while (!localState.checkContinue()) {
        const result = await actualPromise(…);
        localState = localState.changeStateBasedOnResult(result);
    }
    return localState;
}
let promises = [];
for (let i=0; i<10; i++) {
    promises.push(doSomethingAsynchronously());
}

Promise.all(promises).then(arrayOfResults => {
    // all promises finished
});
async function readFromAllPages() {
    let shouldContinue = true;
    let pageId = 0;
    let items = [];

    while (shouldContinue) {
        // fetch the next page
        let result = await fetchSinglePage(pageId);

        // store items
        items.push.apply(items, result.items);

        // evaluate whether we want to continue
        if (!result.items.length) {
            shouldContinue = false;
        }
        pageId++;
    }

    return items;
}

readFromAllPages().then(allItems => {
    // items have been read from all pages
});
function readFromAllPages() {
    let items = [];
    function readNextPage(pageId) {
        return fetchSinglePage(pageId).then(result => {
            items.push.apply(items, result.items);

            if (!result.items.length) {
                return Promise.resolve(null);
            }
            return readNextPage(pageId + 1);
        });
    }
    return readNextPage(0).then(() => items);
}
function readFromAllPages(done=function(){}, pageId=0, res=[]) {
    fetchSinglePage(pageId, res => {
        if (res.items.length) {
            readFromAllPages(done, ++pageId, items.concat(res.items));
        } else {
            done(items);
        }
    });
}

readFromAllPages(allItems => {
    // items have been read from all pages
});
function asyncRecursor(subject, testFn, workFn) {
    // asyncRecursor orchestrates the recursion
    if(testFn(subject)) {
        return Promise.resolve(workFn(subject)).then(result => asyncRecursor(result, testFn, workFn));
        // the `Promise.resolve()` wrapper safeguards against workFn() not being thenable.
    } else {
        return Promise.resolve(subject);
        // the `Promise.resolve()` wrapper safeguards against `testFn(subject)` failing at the first call of asyncRecursor().
    }
}
// example caller
function someBusinessOrientedCallerFn(state_obj) { 
    // ... preamble ...
    return asyncRecursor(
        state_obj, // or state_obj.cloneMe() if necessary
        (obj) => obj.checkContinue(), // testFn
        (obj) => somethingAsync(...).then((result) => { // workFn
            obj.changeStateBasedOnResult(result);
            return obj; // return `obj` or anything you like providing it makes a valid parameter to be passed to `testFn()` and `workFn()` at next recursion.
        });
    );
}