Javascript javascript中的递归承诺

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29020722/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:45:47  来源:igfitidea点击:

Recursive Promise in javascript

javascriptrecursionpromise

提问by dx_over_dt

I'm writing a Javascript Promisethat finds the final redirect URL of a link.

我正在编写一个 JavascriptPromise来查找链接的最终重定向 URL。

What I'm doing is making a HEADrequest in a Promiseusing an XMLHttpRequest. Then, on load, check the HTTP Status for something in the 300 range, or if it has a responseURLattached to the object and that url is different than the it was one handed.

我正在做的是HEADPromise使用XMLHttpRequest. 然后,在加载时,检查 HTTP 状态是否在 300 范围内,或者它是否responseURL附加到对象并且该 url 与单手的不同。

If neither of these are true, I resolve(url). Otherwise, I recursively call getRedirectUrl()on the response URL, and resolve().

如果这些都不是真的,我resolve(url). 否则,我会递归调用getRedirectUrl()响应 URL,并且resolve().

Here's my code:

这是我的代码:

function getRedirectUrl(url, maxRedirects) {
    maxRedirects = maxRedirects || 0;
    if (maxRedirects > 10) {
        throw new Error("Redirected too many times.");
    }

    var xhr = new XMLHttpRequest();
    var p = new Promise(function (resolve) {
        xhr.onload = function () {
            var redirectsTo;
            if (this.status < 400 && this.status >= 300) {
                redirectsTo = this.getResponseHeader("Location");
            } else if (this.responseURL && this.responseURL != url) {
                redirectsTo = this.responseURL;
            }

            if (redirectsTo) {
                // check that redirect address doesn't redirect again
                // **problem line**
                p.then(function () { self.getRedirectUrl(redirectsTo, maxRedirects + 1); });
                resolve();
            } else {
                resolve(url);
            }
        }

        xhr.open('HEAD', url, true);
        xhr.send();
    });

    return p;
}

Then to use this function I do something like:

然后要使用此功能,我会执行以下操作:

getRedirectUrl(myUrl).then(function (url) { ... });

The issue is that resolve();in getRedirectUrlwill call the then()from the calling function before it calls the getRedirectUrlrecursive call, and at that point, the URL is undefined.

问题是resolve();ingetRedirectUrlthen()在调用getRedirectUrl递归调用之前从调用函数调用 ,此时 URL 为undefined.

I tried, rather than p.then(...getRedirectUrl...)doing return self.getRedirectUrl(...)but this will never resolve.

我尝试过,而不是p.then(...getRedirectUrl...)这样做,return self.getRedirectUrl(...)但这永远不会解决。

My guess is that the pattern I'm using (that I basically came up with on the fly) isn't right, altogether.

我的猜测是我正在使用的模式(我基本上是即时想到的)完全不正确。

回答by JLRishe

The problem is that the promise you return from getRedirectUrl()needs to include the entire chain of logic to get to the URL. You're just returning a promise for the very first request. The .then()you're using in the midst of your function isn't doing anything.

问题是您返回的承诺getRedirectUrl()需要包含整个逻辑链才能到达 URL。您只是为第一个请求返回一个承诺。在.then()您使用的是你的函数的中间没有做任何事情。

To fix this:

要解决此问题:

Create a promise that resolves to redirectUrlfor a redirect, or nullotherwise:

创建一个解析redirectUrl为重定向的承诺,或null以其他方式:

function getRedirectsTo(xhr) {
    if (xhr.status < 400 && xhr.status >= 300) {
        return xhr.getResponseHeader("Location");
    }
    if (xhr.responseURL && xhr.responseURL != url) {
        return xhr.responseURL;
    }

    return null;
}

var p = new Promise(function (resolve) {
    var xhr = new XMLHttpRequest();

    xhr.onload = function () {
        resolve(getRedirectsTo(xhr));
    };

    xhr.open('HEAD', url, true);
    xhr.send();
});

Use .then()on thatto return the recursive call, or not, as needed:

使用.then()返回递归调用,还是不行,需要:

return p.then(function (redirectsTo) {
    return redirectsTo
        ? getRedirectUrl(redirectsTo, redirectCount+ 1)
        : url;
});

Full solution:

完整解决方案:

function getRedirectsTo(xhr) {
    if (xhr.status < 400 && xhr.status >= 300) {
        return xhr.getResponseHeader("Location");
    }
    if (xhr.responseURL && xhr.responseURL != url) {
        return xhr.responseURL;
    }

    return null;
}

function getRedirectUrl(url, redirectCount) {
    redirectCount = redirectCount || 0;

    if (redirectCount > 10) {
        throw new Error("Redirected too many times.");
    }

    return new Promise(function (resolve) {
        var xhr = new XMLHttpRequest();

        xhr.onload = function () {
            resolve(getRedirectsTo(xhr));
        };

        xhr.open('HEAD', url, true);
        xhr.send();
    })
    .then(function (redirectsTo) {
        return redirectsTo
            ? getRedirectUrl(redirectsTo, redirectCount + 1)
            : url;
    });
}

回答by cuddlemeister

Here's the simplified solution:

这是简化的解决方案:

const recursiveCall = (index) => {
    return new Promise((resolve) => {
        console.log(index);
        if (index < 3) {
            return resolve(recursiveCall(++index))
        } else {
            return resolve()
        }
    })
}

recursiveCall(0).then(() => console.log('done'));

回答by Stephen Quan

The following has two functions:

下面有两个功能:

  • _getRedirectUrl - which is a setTimeout object simulation for looking up a single step lookup of a redirected URL (this is equivalent to a single instance of your XMLHttpRequest HEAD request)
  • getRedirectUrl - which is recursive calls Promises to lookup the redirect URL
  • _getRedirectUrl - 这是一个 setTimeout 对象模拟,用于查找重定向 URL 的单步查找(这相当于 XMLHttpRequest HEAD 请求的单个实例)
  • getRedirectUrl - 这是递归调用 Promises 以查找重定向 URL

The secret sauce is the sub Promise whose's successful completion will trigger a call to resolve() from the parent promise.

秘诀是子 Promise,它的成功完成将触发父 Promise 对 resolve() 的调用。

function _getRedirectUrl( url ) {
    return new Promise( function (resolve) {
        const redirectUrl = {
            "https://mary"   : "https://had",
            "https://had"    : "https://a",
            "https://a"      : "https://little",
            "https://little" : "https://lamb",
        }[ url ];
        setTimeout( resolve, 500, redirectUrl || url );
    } );
}

function getRedirectUrl( url ) {
    return new Promise( function (resolve) {
        console.log("* url: ", url );
        _getRedirectUrl( url ).then( function (redirectUrl) {
            // console.log( "* redirectUrl: ", redirectUrl );
            if ( url === redirectUrl ) {
                resolve( url );
                return;
            }
            getRedirectUrl( redirectUrl ).then( resolve );
        } );
    } );
}

function run() {
    let inputUrl = $( "#inputUrl" ).val();
    console.log( "inputUrl: ", inputUrl );
    $( "#inputUrl" ).prop( "disabled", true );
    $( "#runButton" ).prop( "disabled", true );
    $( "#outputLabel" ).text( "" );
    
    getRedirectUrl( inputUrl )
    .then( function ( data ) {
        console.log( "output: ", data);
        $( "#inputUrl" ).prop( "disabled", false );
        $( "#runButton" ).prop( "disabled", false );
        $( "#outputLabel").text( data );
    } );

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Input:

<select id="inputUrl">
    <option value="https://mary">https://mary</option>
    <option value="https://had">https://had</option>
    <option value="https://a">https://a</option>
    <option value="https://little">https://little</option>
    <option value="https://lamb">https://lamb</option>
</select>

Output:

<label id="outputLabel"></label>

<button id="runButton" onclick="run()">Run</button>

As another illustration of recursive Promises, I used it to solve a maze. The Solve()function is invoked recursively to advance one step in a solution to a maze, else it backtracks when it encounters a dead end. The setTimeoutfunction is used to set the animation of the solution to 100ms per frame (i.e. 10hz frame rate).

作为递归承诺的另一个例子,我用它来解决一个迷宫。该Solve()函数被递归调用以在迷宫的解决方案中前进一步,否则在遇到死胡同时回溯。该setTimeout函数用于设置解的动画为每帧100ms(即10hz帧率)。

const MazeWidth = 9
const MazeHeight = 9

let Maze = [
    "# #######",
    "#   #   #",
    "# ### # #",
    "# #   # #",
    "# # # ###",
    "#   # # #",
    "# ### # #",
    "#   #   #",
    "####### #"
].map(line => line.split(''));

const Wall = '#'
const Free = ' '
const SomeDude = '*'

const StartingPoint = [1, 0]
const EndingPoint = [7, 8]

function PrintDaMaze()
{
    //Maze.forEach(line => console.log(line.join('')))
    let txt = Maze.reduce((p, c) => p += c.join('') + '\n', '')
    let html = txt.replace(/[*]/g, c => '<font color=red>*</font>')
    $('#mazeOutput').html(html)
}

function Solve(X, Y) {

    return new Promise( function (resolve) {
    
        if ( X < 0 || X >= MazeWidth || Y < 0 || Y >= MazeHeight ) {
            resolve( false );
            return;
        }
        
        if ( Maze[Y][X] !== Free ) {
            resolve( false );
            return;
        }

        setTimeout( function () {
        
            // Make the move (if it's wrong, we will backtrack later)
            Maze[Y][X] = SomeDude;
            PrintDaMaze()

            // Check if we have reached our goal.
            if (X == EndingPoint[0] && Y == EndingPoint[1]) {
                resolve(true);
                return;
            }

            // Recursively search for our goal.
            Solve(X - 1, Y)
            .then( function (solved) {
                if (solved) return Promise.resolve(solved);
                return Solve(X + 1, Y);
            } )
            .then( function (solved) {
                if (solved) return Promise.resolve(solved);
                return Solve(X, Y - 1);
             } )
             .then( function (solved) {
                if (solved) return Promise.resolve(solved);
                return Solve(X, Y + 1);
             } )
             .then( function (solved) {
                 if (solved) {
                     resolve(true);
                     return;
                 }

                 // Backtrack
                 setTimeout( function () {
                     Maze[Y][X] = Free;
                     PrintDaMaze()
                     resolve(false);
                 }, 100);
                 
             } );

        }, 100 );
    } );
}

Solve(StartingPoint[0], StartingPoint[1])
.then( function (solved) {
    if (solved) {
        console.log("Solved!")
        PrintDaMaze()
    }
    else
    {
        console.log("Cannot solve. :-(")
    }
} );
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<pre id="mazeOutput">
</pre>

回答by Mayur S

Please check below example it will return factorialof a given number as we did in many programming languages.

请检查下面的示例,它会factorial像我们在许多编程语言中所做的那样返回给定的数字。

I have implemented below example using JavaScriptpromises.

我已经使用JavaScript承诺实现了以下示例。

let code = (function(){
 let getFactorial = n =>{
  return new Promise((resolve,reject)=>{
   if(n<=1){
    resolve(1);
   }
   resolve(
    getFactorial(n-1).then(fact => {
     return fact * n;
    })
   )
  });
 }
 return {
  factorial: function(number){
   getFactorial(number).then(
    response => console.log(response)
   )
  }
 }
})();
code.factorial(5);
code.factorial(6);
code.factorial(7);

回答by dx_over_dt

If you're in an environment that supports async/await(virtually all modern environments do), you can write an async functionthat looks a bit more like a recursive function pattern we all know and love. It's not possible to completely avoid a Promisedue to the nature of XMLHttpRequestonly retrieving a value via the loadevent (rather than exposing a Promiseitself), but the recursive nature of the function that makes the call should look familiar.

如果您在一个支持async/的环境中await(几乎所有现代环境都支持),您可以编写一个async function看起来更像我们都知道和喜爱的递归函数模式的环境。Promise由于XMLHttpRequest仅通过load事件检索值(而不是公开 aPromise本身)的性质,不可能完全避免 a ,但进行调用的函数的递归性质应该看起来很熟悉。

Having four more years of JavaScript experience than I had when I originally wrote this question, I cleaned up the code a bit, but it works essentially the same way.

与最初编写此问题时相比,我拥有 4 年以上的 JavaScript 经验,我对代码进行了一些清理,但它的工作方式基本相同。

// creates a simple Promise that resolves the xhr once it has finished loading
function createXHRPromise(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();

        // addEventListener('load', ...) is basically the same as setting
        // xhr.onload, but is better practice
        xhr.addEventListener('load', () => resolve(xhr));

        // throw in some error handling so that the calling function 
        // won't hang
        xhr.addEventListener('error', reject);
        xhr.addEventListener('abort', reject);

        xhr.open('HEAD', url, true);
        xhr.send();
    });
}

async function getRedirectUrl(url, maxRetries = 10) {
    if (maxRetries <= 0) {
        throw new Error('Redirected too many times');
    }

    const xhr = await createXHRPromise(url);
    if (xhr.status >= 300 && xhr.status < 400) {
        return getRedirectUrl(xhr.getResponseHeader("Location"), maxRetries - 1);
    } else if (xhr.responseURL && xhr.responseURL !== url) {
        return getRedirectUrl(xhr.responseURL, maxRetries - 1);
    }

    return url;
}

A brief explanation of async/await

简要说明async/await

  • an async functionis syntactic sugar for a Promise
  • awaitis syntactic sugar for Promise.then()
  • returnwithin an async functionis syntactic sugar for resolve()
  • throwwithin an async functionis syntactic sugar for reject()
  • anasync function是 a 的语法糖Promise
  • await是语法糖 Promise.then()
  • return在 an 中async function是语法糖resolve()
  • throw在 an 中async function是语法糖reject()

If an async functionreturns either another async functioncall or a Promise, the function/promise will resolve before the original call resolves, exactly the same way that resolving a Promisewould in the Promisepattern.

如果 anasync function返回另一个async function调用或 a Promise,则函数/promise 将在原始调用解析之前解析,PromisePromise模式中解析 a 的方式完全相同。

So, you can call getRedirectUrl(someUrl).then(...).catch(...)exactly the same way the original question would have.

因此,您可以getRedirectUrl(someUrl).then(...).catch(...)按照与原始问题完全相同的方式进行调用。

It should probably be noted that using an XHR to resolve a redirected URL will fail for any URL that doesn't include the proper CORS header.

应该注意的是,对于不包含正确 CORS 标头的任何 URL,使用 XHR 解析重定向 URL 将失败。



As an added bonus, async/await makes an iterative approach trivial.

作为额外的奖励,async/await 使迭代方法变得微不足道。

async function getRedirectUrl(url, maxRetries = 10) {
    for (let i = 0; i < maxRetries; i++) {
        const xhr = await createXHRPromise(url);
        if (xhr.status >= 300 && xhr.status < 400) {
            url = xhr.getResponseHeader("Location");
        } else if (xhr.responseURL && xhr.responseURL !== url) {
            url = xhr.responseURL;
        } else {
            return url;
        }
    }

    throw new Error('Redirected too many times');
}