获取多个承诺,return 只有一个

Fetch multiple promises, return only one

我有一个要获取的网址列表。所有这些 url return 都是一个带有 属性 valid 的 json 对象。但是只有一个获取承诺具有 valid 属性 到 true.

的魔力

我尝试了 url.forEach(...)Promises.all([urls]).then(...) 的各种组合。目前我的设置是:

const urls = [
    'https://testurl.com', 
    'https://anotherurl.com', 
    'https://athirdurl.com' // This is the valid one
];

export function validate(key) {
    var result;
    urls.forEach(function (url) {
        result = fetch(`${url}/${key}/validate`)
            .then((response) => response.json())
            .then((json) => {
                if (json.license.valid) {
                    return json;
                } else {
                   Promise.reject(json);
                }
            });
    });

    return result;
}

由于异步承诺,以上内容不起作用。当第一个 valid == true 被点击时,我如何迭代我的 url 和 return?

请注意,validate 不可能 return 结果 (see here for why)。但它可以 return promise 结果。

您想要的是 Promise.race 相似,但不完全相同(如果 fetch 承诺之一,Promise.race 会拒绝在另一个使用 valid = true 解决之前被拒绝)。因此,只需创建一个承诺并在您获得 valid 为真的第一个解决方案时解决它:

export function validate(key) {
    return new Promise((resolve, reject) => {
        let completed = 0;
        const total = urls.length;
        urls.forEach(url => {
            fetch(`${url}/${key}/validate`)
                .then((response) => {
                    const json = response.json();
                    if (json.license.valid) {
                        resolve(json);
                    } else {
                        if (++completed === total) {
                            // None of them had valid = true
                            reject();
                        }
                    }
                })
                .catch(() => {
                    if (++completed === total) {
                        // None of them had valid = true
                        reject();
                    }
                });
        });
    });
}

注意失败案例的处理。

请注意,如果您愿意,可以分解出这两个 completed 检查:

export function validate(key) {
    return new Promise((resolve, reject) => {
        let completed = 0;
        const total = urls.length;
        urls.forEach(url => {
            fetch(`${url}/${key}/validate`)
                .then((response) => {
                    const json = response.json();
                    if (json.license.valid) {
                        resolve(json);
                    }
                })
                .catch(() => {
                    // Do nothing, converts to a resolution with `undefined`
                })
                .then(() => {
                    // Because of the above, effectively a "finally" (which we
                    // may get on Promises at some point)
                    if (++completed === total) {
                        // None of them had valid = true.
                        // Note that we come here even if we've already
                        // resolved the promise -- but that's okay(ish), a
                        // promise's resolution can't be changed after it's
                        // settled, so this would be a no-op in that case
                        reject();
                    }
                });
        });
    });
}

如果你想避免测试每个URL,你可以使用下面的代码。

const urls = [
    'https://testurl.com', 
    'https://anotherurl.com', 
    'https://athirdurl.com' // This is the valid one
];

export function validate(key) {
 return new Promise((resolve, reject) => {
  function testUrl(url) {
     fetch(`${url}/${key}/validate`)
       .then((response) => response.json())
        .then((json) => {
         if (json.license.valid) {
            resolve(json);
           return;
          }
          if (urlIndex === urls.length) {
            reject("No matches found...");
            return;
          }
          testUrl(urls[urlIndex++]);
        });
    }

  let urlIndex = 0;
    if (!urls.length)
     return reject("No urls to test...");
    testUrl(urls[urlIndex++]);
  });
}

这可以使用 SynJS 来完成。这是一个工作示例:

var SynJS = require('synjs');
var fetchUrl = require('fetch').fetchUrl;

function fetch(context,url) {
    console.log('fetching started:', url);
    var result = {};
    fetchUrl(url, function(error, meta, body){
        result.done = true;
        result.body = body;
        result.finalUrl = meta.finalUrl; 
        console.log('fetching finished:', url);
        SynJS.resume(context);
    } );

    return result;
}

function myFetches(modules, urls) {
    for(var i=0; i<urls.length; i++) {
        var res = modules.fetch(_synjsContext, urls[i]);
        SynJS.wait(res.done);
        if(res.finalUrl.indexOf('github')>=0) {
            console.log('found correct one!', urls[i]);
            break;
        }
    }
};

var modules = {
        SynJS:  SynJS,
        fetch:  fetch,
};

const urls = [
              'http://www.google.com', 
              'http://www.yahoo.com', 
              'http://www.github.com', // This is the valid one
              'http://www.wikipedia.com'
          ];

SynJS.run(myFetches,null,modules,urls,function () {
    console.log('done');
});

它将产生以下输出:

fetching started: http://www.google.com
fetching finished: http://www.google.com
fetching started: http://www.yahoo.com
fetching finished: http://www.yahoo.com
fetching started: http://www.github.com
fetching finished: http://www.github.com
found correct one! http://www.github.com
done

让我加入一个不错的紧凑型组合

它使用 Promise.all,但是每个内部 Promise 都会捕获任何错误并在这种情况下简单地解析为 false,因此 Promise.all 永远不会拒绝 - 任何完成的提取,但没有 license.valid 也会解析为 false

进一步处理数组 Promise.all 解析,过滤掉 false 值,并返回第一个(从问题描述中应该是唯一的)有效 JSON 响应

const urls = [
    'https://testurl.com', 
    'https://anotherurl.com', 
    'https://athirdurl.com' // This is the valid one
];

export function validate(key) {
    return Promise.all(urls.map(url => 
        fetch(`${url}/${key}/validate`)
        .then(response => response.json())
        .then(json => json.license && json.license.valid && json)
        .catch(error => false)
    ))
    .then(results => results.filter(result => !!result)[0] || Promise.reject('no matches found'));
}