javascript中的递归承诺

b91juud3  于 2023-02-02  发布在  Java
关注(0)|答案(7)|浏览(144)

我正在写一个Javascript Promise,它可以找到链接的最终重定向URL。
我所做的是使用XMLHttpRequestPromise中发出HEAD请求。然后,在加载时,检查HTTP状态是否在300范围内,或者是否有responseURL附加到对象,并且该URL与单手操作时的URL不同。
如果这两个都不为真,则调用resolve(url),否则,在响应URL上递归调用getRedirectUrl(),然后调用resolve()
下面是我的代码:

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

为了使用这个函数,我做了如下操作:

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

问题是getRedirectUrl中的resolve();在调用getRedirectUrl递归调用之前将从调用函数调用then(),此时,URL为undefined
我试过了,而不是p.then(...getRedirectUrl...)return self.getRedirectUrl(...),但这永远不会解决。
我的猜测是,我使用的模式(基本上是我匆忙想出的)完全不正确。

bvk5enib

bvk5enib1#

问题在于,从getRedirectUrl()返回的承诺需要包括到达URL的整个逻辑链,您只是返回了第一个请求的承诺,而在函数中间使用的.then()没有做任何事情。
要解决此问题:
创建解析为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();
});

根据需要,在 * that * 上使用.then()返回递归调用或不返回:

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

完整解决方案:

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

7xllpg7q2#

以下是简化的解决方案:

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

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

0md85ypi3#

以下有两个功能:

  • _getRedirectUrl-这是一个setTimeout对象模拟,用于查找重定向URL的单步查找(这等效于XMLHttpRequest HEAD请求的单个示例)
  • getRedirectUrl-递归调用Promises以查找重定向URL

秘密武器是子承诺,它的成功完成将触发父承诺调用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>

作为递归Promises的另一个例子,我用它来解决一个迷宫。Solve()函数被递归调用来推进迷宫解决方案中的一个步骤,否则当它遇到死胡同时它会后退。setTimeout函数被用来将解决方案的动画设置为每帧100ms(即10hz帧速率)。
一个二个一个一个

rjee0c15

rjee0c154#

请检查下面的示例,它将返回给定数字的**factorial,就像我们在许多编程语言中所做的那样。
我已经使用
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);
hfsqlsce

hfsqlsce5#

如果您所在的环境支持async/await(实际上所有现代环境都是这样),您可以编写一个async function,它看起来更像是我们都知道并喜爱的递归函数模式,但由于XMLHttpRequest只能通过load事件检索值,因此不可能完全避免使用Promise(而不是公开Promise本身),但是进行调用的函数的递归性质应该看起来很熟悉。
我的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;
}

async/await的简要说明

  • async functionPromise的语法糖
  • awaitPromise.then()的语法糖
  • async function中的returnresolve()的语法糖
  • async function中的throwreject()的语法糖

如果async function返回另一个async function调用或Promise,则函数/promise将在原始调用解析之前解析,与Promise模式中解析Promise的方式完全相同。
因此,您可以按照与原始问题完全相同的方式调用getRedirectUrl(someUrl).then(...).catch(...)
应该注意的是,使用XHR解析重定向的URL对于任何不包含正确CORS头的URL都将失败。
作为一个额外的好处,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');
}

另一个注意事项:现代的浏览器有一个fetch()函数,它基本上做createXHRPromise()所做的事情,但是更加通用。node不支持它,但是有一个npm包叫做node-fetch

j5fpnvbx

j5fpnvbx6#

请检查下面的例子中的递归承诺在javascript/typescript,它不会解决承诺,直到数字增加到大于13.
下面的代码适合于typescript,并针对javascript稍作修改。

async iterate(number: number): Promise<any> {
        return new Promise((resolve, reject) => {
            let number = 0;
            if (number > 13) {
                // recursive terminate condition
                resolve(number);
                return;
            } else {
                number = number + 1;
                // recursive call
                this.iterate(number).then(resolve);
            }

        });
    }



this.iterate().then((resolvedData: any) => {
           // wait until number is not greater than 13
           console.log(resolvedData);
    });
nhjlsmyf

nhjlsmyf7#

如果你有一个嵌套的数组结构,并且有异步调用,这个解决方案(建立在前面的答案之上)可能会有帮助。这个例子为它在一个(可能的)嵌套数组中找到的每个值运行一个setTimeout(),并在处理完所有的值后进行解析:

const recursiveCall = (obj) => {
    return new Promise((resolve) => {
        if(obj instanceof Array){
            let cnt = obj.length;
            obj.forEach(el => {
                recursiveCall(el)
                .then(() => {
                    if(!--cnt)return resolve();
                })
                
            });
        } else {
            setTimeout(() => {
                console.log(obj);
                return resolve();
            }, obj);
            
        }
    })
}

recursiveCall([100,50,[10,[200, 300],30],1]).then(() => console.log('done'));

>1
>10
>30
>50
>100
>200
>300
>done

相关问题