为了账号安全,请及时绑定邮箱和手机立即绑定

异步承诺使用未知的异步值提供递归

异步承诺使用未知的异步值提供递归

幕布斯7119047 2021-11-12 10:45:34
我对异步承诺递归感到头疼。我有一堆承诺在下载异步数据时解决(由 Promise.all 组合)。但有时在我刚刚下载的数据中有指向另一个数据的链接,那必须是下载(递归)。最好的解释是显示我猜的代码。注释在代码中。(我尝试了各种组合都无济于事。)var urls = ['http://czyprzy.vdl.pl/file1.txt', 'http://czyprzy.vdl.pl/file2.txt', 'http://czyprzy.vdl.pl/file3.txt'];var urlsPromise = [];var secondPart = [];var thirdPart = [];function urlContent(url, number) {  return new Promise(function (resolve) {    var dl = request(url, function (err, resp, content) {      if (err || resp.statusCode >= 400) {        return resolve({number : number, url : url, error : 'err'});      }      if (!err && resp.statusCode == 200) {        if (content.indexOf('file') !== -1) // if there is 'file' inside content we need (would like to :) download this new file by recursion        {            content = content.slice(content.indexOf('file') + 4);            content =+ content; // (number to pass later on, so we know what file we are working on)            url = 'http://czyprzy.vdl.pl/file' + content + '.txt'; // (we build new address)            //urlsPromise.push(urlContent(url, content)); // this will perform AFTER Promise.all(urlsPromise) so we simply can't do recurention (like that) here            secondPart.push(urlContent(url, content)); // if we use another promise array that put resolved items to that array everything will work just fine - but only till first time, then we would need to add another (thirdPart) array and use another Promise.all(thirdPart)... and so on and so on... --- the problem is I don't know how many files there will be, so it means I have no idea how many 'parts' for Promise.all I need to create, some kind of asynchronous loop/recursion would save me here, but I don't know how to do that properly so the code can run in proper order        }        return resolve({number : number, url : url}); // this goes to 'urlsPromise' array      }    });  });}if (urls.length !== 0) {  for (var i = 0; i < urls.length; i++)  {urlsPromise.push(urlContent(urls[i], i + 1));}}
查看完整描述

2 回答

?
大话西游666

TA贡献1817条经验 获得超14个赞

await 关键字可以大大简化这一点。您不需要使用自递归函数。此演示使用随机大小的数组伪造服务器调用。


https://jsfiddle.net/mvwahq19/1/


// setup: create a list witha  random number of options.

var sourceList = [];

var numItems = 10 + Math.floor(Math.random() * 20);


for (var i = 0; i < numItems; i++)

{

    sourceList.push(i);

}


sourceList.push(100);


var currentIndex = 0;


// a function which returns a promise. Imagine it is asking a server.

function getNextItem() {

    var item = sourceList[currentIndex];

    currentIndex++;

    

    return new Promise(function(resolve) {

        setTimeout(function() {

          resolve(item);

        }, 100);

    });

}


async function poll() {

    var collection = [];

    var done = false;

    while(!done) {

        var item = await getNextItem();

        

        collection.push(item);

        

        console.log("Got another item", item);

        

        if (item >= 100) {

            done = true;

        }

    }

    console.log("Got all items", collection);

}


poll();

你可以写一个普通的 for 循环,除了内容使用 await。


查看完整回答
反对 回复 2021-11-12
?
胡说叔叔

TA贡献1804条经验 获得超8个赞


当我直接问他这个问题时,他用时间和知识支持我并给出了这个很好的答案。


代码:


//// files LINKING (those files do exist on live server - just for testing purposes):

// file1->file4(AND file101)->file7->file10  /-/ file1 content: file4 /-/ file4 content: file7 /-/ file7 content: file10 /-/ file10 content: EMPTY /-/ file101 content: EMPTY

// file2->file5(AND file102)->file8->file11  /-/ file2 content: file5 /-/ file5 content: file8 /-/ file8 content: file11 /-/ file11 content: EMPTY /-/ file102 content: EMPTY

// file3->file6(AND file103)->file9->file12  /-/ file3 content: file6 /-/ file6 content: file9 /-/ file9 content: file12 /-/ file12 content: EMPTY /-/ file103 content: EMPTY


var urls = ['http://czyprzy.vdl.pl/file1.txt', 'http://czyprzy.vdl.pl/file2.txt', 'http://czyprzy.vdl.pl/file3.txt'];

var urlsPromise = [];


function requestPromise(url) {

    return new Promise(function(resolve, reject) {

        request(url, function (err, resp, content) {

            if (err || resp.statusCode != 200) reject(err || resp.statusCode);

            else resolve(content);

        });

    });

}


async function urlContent(url, number) {

  var arr = [];

  let content = await requestPromise(url);


  while (content.indexOf(';') !== -1)

  {

    var semiColon = content.indexOf(';');

var fileLink = content.slice(content.indexOf('file'), semiColon + 1);

content = content.replace(fileLink, ''); // we need to remove the file link so we won't iterate over it again, we will add to the array only new links

var fileLinkNumber = fileLink.replace('file', '');

fileLinkNumber = fileLinkNumber.replace(';', '');

fileLinkNumber =+ fileLinkNumber;

url = 'http://czyprzy.vdl.pl/file' + fileLinkNumber + '.txt'; // we build new address

arr.push({url, fileLinkNumber});

}

if (content.indexOf('file') !== -1)

{

var fileLinkNumber = content.slice(content.indexOf('file') + 4);

fileLinkNumber =+ fileLinkNumber;

  url = 'http://czyprzy.vdl.pl/file' + fileLinkNumber + '.txt';

  arr.push({url, fileLinkNumber});

}

var newArr = arr.map(function(item)

{

return urlContent(item.url, item.fileLinkNumber); // return IS important here

});

return [].concat(arr, ...await Promise.all(newArr));

}


async function doing() {

    let urlsPromise = [];

    for (let i = 0; i < urls.length; i++) {

        urlsPromise.push(urlContent(urls[i], i + 1));

    }

    let results = [].concat(...await Promise.all(urlsPromise)); // flatten the array of arrays

    console.log(results);

}


//// this is only to show Promise.all chaining - so you can do async loop, and then wait for some another async data - in proper chain.


var test_a = ['http://czyprzy.vdl.pl/css/1.css', 'http://czyprzy.vdl.pl/css/2.css', 'http://czyprzy.vdl.pl/css/cssa/1a.css', 'http://czyprzy.vdl.pl/css/cssa/2a.css'];

var promisesTest_a = [];


function requestStyle(url)

{

return new Promise(function(resolve, reject)

{

request(url, function(error, response, content)

{

if (response.statusCode === 200 && !error)

{resolve(content);}

else

{reject(error);}

});

});

}


for (var i = 0; i < test_a.length; i++)

{promisesTest_a.push(requestStyle(test_a[i]));}


Promise.all(promisesTest_a).then(function(promisesTest_a)

{

   console.log(promisesTest_a);

}).then(function()

{

   console.log('\nNow we start with @imports...\n');

}).then(function()

{

   return doing();

}).then(function()

{

   console.log('ALL DONE!');

});

评论:


首先解释什么是 [...] - 解构的休息参数(以防万一,如果你不知道的话)。


var arr = [];

var array1 = ['one', 'two', 'three']

var array2 = [['four', 'five', ['six', 'seven']], 'eight', 'nine', 'ten'];

arr = array1.concat(array2);

console.log(arr); // it does not flattern the array - it just concatenate them (join them together)

console.log('---');

// however

arr = array1.concat(...array2);

console.log(arr); // notice the [...] - as you can see it flatern the array - 'four' and 'five' are pull out of an array - think of it as level up :) remember that it pull up WHOLE array that is deeper - so 'six' and 'seven' are now 1 level deep (up from 2 levels deep, but still in another array).

console.log('---');

// so

arr = [].concat(...arr);

console.log(arr); // hurrrray our array is flat (single array without nested elements)

console.log();

所有准备下载的文件(链接)(urls数组中的 3 个起始文件)几乎立即下载(同步循环包含它们的数组 - 一个接一个,但很快,因为我们只是迭代它们以同步方式)。


然后,当我们获得它们的内容时(因为我们等待内容下载 - 所以我们在这里得到了一个已解析的承诺数据)我们开始寻找与我们已经获得的相关的其他可能的 url(文件)的信息,以下载它们(通过异步递归)。


当我们找到有关可能的附加 url/文件的所有信息(以正则表达式数组表示 -匹配)时,我们将其推送到数据数组(在我们的代码中命名为arr)并下载它们(感谢 url 的变化)。


我们下载它们通过回归异步 urlContent功能需要等待的requestPromise承诺(所以我们有决心/拒绝的数据urlContent所以如果需要,我们可以变异它-构建正确的URL,以获得下一个文件/内容)。


依此类推,直到我们“迭代”(下载)所有文件。每次调用 urlContent 时,它都会返回一个最初未决的承诺数组(promises 变量)。当我们等待 Promise.all(promises) 时,只有当所有这些承诺都已解决时,才会在那个位置恢复执行。所以,在那一刻,我们拥有每一个承诺的价值。每一个都是一个数组。我们使用一个 big concat 将所有这些数组组合成一个大数组,还包括arr的元素(我们需要记住从我们已经下载的文件中下载的文件可能超过 1 个——这就是我们存储值的原因在数据数组中 -在代码中命名为arr - 存储promiseReques函数解析/拒绝值)。这个“大”数组是用于解决承诺的值。回想一下,这个 promise 是当前函数上下文已经返回的那个,在第一个 await 被执行时。


这是很重要的部分 - 所以它 (urlContent) 返回 (await) 一个单一的承诺,并且这个(返回的)承诺被解析为一个数组作为值。请注意,当遇到第一个 await 时,异步函数会立即将承诺返回给调用者。async 函数中的 return 语句决定了返回的 promise 的解析值是什么。在我们的例子中,这是一个数组。


因此,每次调用时 urlContent 都会返回一个承诺 - 数组中已解析的值 - [...](解构的其余参数 - 返回一个最终解析为数组的承诺),由我们的异步执行函数收集(导致 3 个 url 被触发在开始时 - 每个人都有自己的 urlContent 函数...路径),从 Promise.all(urlsPromise) 收集(等待!)所有这些数组,以及当它们被解析时(我们等待它们被解析并由 Promise 传递) .all)它“返回”您的数据(结果变量)。准确地说,做返回一个承诺(因为它是异步的)。但是我们称之为做的方式,我们表明我们对这个承诺解决的问题不感兴趣,事实上,因为做没有 return 语句,该承诺解析为 UNDEFINED (!)。无论如何,我们不使用它 - 我们只是将结果输出到控制台。


与异步函数混淆的一件事是,当函数返回时不执行return 语句(名称中有什么,对吧!?;)。该函数在执行第一个 await 时已经返回。当最终它执行 return 语句时,它并没有真正返回一个值,而是解析“自己的”承诺;它之前返回的那个。如果我们真的想将输出与逻辑分开,我们不应该在那里做 console.log(results) ,而是要返回结果,然后,在我们称之为做的地方,我们可以做 dodo.then(console.log); 现在我们确实使用了 do 返回的 promise!


我会保留动词“返回”来表示函数的调用者同步返回的内容。我将使用“解决”作为将承诺设置为具有值的已解决状态的操作,该值可以通过await或.then()访问。


查看完整回答
反对 回复 2021-11-12
  • 2 回答
  • 0 关注
  • 130 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信