使用Promises和Fetch API在JavaScript中获取多个文件

4

我正在学习Promises提升我的javascript技能,已经有一个使用XHR和回调函数的库来一次性加载和注入多个文件,并且只有在所有文件都加载成功的情况下才会继续执行。

我想使用Promise.all()与Fetch API来实现类似的功能,但是无法使其正常工作:console.log('All the promises are resolved', values);总是会触发,即使其中一些fetch promises失败了。

我希望能够执行以下代码,并且只有在所有文件都被成功获取时才能执行nowInitialize函数,否则通过catch()抛出第一个文件获取失败的原因。

xRequire(['index.html', 'style.cds'])
  .then(nowInitialize)
  .catch(reason => 'One or more files failed to load' + reason)

style.cds 显然会失败。

//TODO Handle file types appropriately
//TODO: Inject css, javascript files

function xRequire(files) {
    let urls = [];
    let promisesList = [];
    let handleAllPromises;


//Populates urls with each file required
for(let i=0; i < files.length ; i++) {
    urls.push(files[i]);
}
//Fetch each file in urls
urls.forEach( (url, i) => { // (1)
    promisesList.push(
        fetch(url)
            .then(handleResponse)
            .then(data => console.log(data))
            .catch(error => console.log(error))
    );
});

handleAllPromises = Promise.all(promisesList);
handleAllPromises.then(function(values) {
    console.log('All the promises are resolved', values);
});
handleAllPromises.catch(function(reason) {
    console.log('One of the promises failed with the following reason', reason);
});
}

function handleResponse(response) {
let contentType = response.headers.get('content-type');
console.log('Requested Info: ' + contentType);
if (contentType.includes('application/json')) {
    return handleJSONResponse(response);
} else if (contentType.includes('text/html')) {
    return handleTextResponse(response);
} else if (contentType.includes('text/css')) {
    return handleTextResponse(response);
} else if (contentType.includes('application/javascript')) {
    return handleTextResponse(response);
} else {
    throw new Error(`Sorry, content-type ${contentType} not supported`);
}
}

function handleJSONResponse(response) {
return response.json()
    .then(json => {
        if (response.ok) {
            return json;
        } else {
            return Promise.reject(Object.assign({}, json, {
                status: response.status,
                statusText: response.statusText
            }));
        }
    });
}

function handleTextResponse(response) {
return response.text()
    .then(text => {
        if (response.ok) {
            return text;
        } else {
            return Promise.reject({
                status: response.status,
                statusText: response.statusText,
                err: text
            });
        }
    });
}

@CertainPerformance,你不是真的认为这不是重复的吧?我的意思是,简单搜索就可以在SO上找到多个多个版本...拜托了。这里有一个例子:https://dev59.com/_1wZ5IYBdhLWcg3wNOCI - Akrion
@Akrion 我没有说那个,我只是说async/await语法问题不是这个问题所问的(这个问题至少存在几个问题)。 - CertainPerformance
3个回答

1

你能把它改写成async-await代码吗?这是典型流程的大致想法:

const [data1, data2, data3] = await Promise.all([
  fetch(url1),
  fetch(url2),
  fetch(url3),
]);

换句话说,Promise.all() 返回从多个 fetch() 函数返回的所有数据的 Promise。接下来,如果将其放入 try-catch 中,您也可以处理拒绝(rejection)。
try {
  const [data1, data2, data3] = await Promise.all([
    fetch(url1),
    fetch(url2),
    fetch(url3),
  ]);

  // Now you can process the data:
  [data1, data2, data3].map(handleResponse);
} catch (error) {
  console.log('Error downloading one or more files:', error);
}

如果您想使用async-await循环,可以这样做:
const promises = [];
for (const url of [url1, url2, url3, url4]) {
  promises.push(fetch(url));
}

const [data1, data2, data3, data4] = await Promise.all(promises);

这看起来很不错,但是当循环遍历所有被请求的文件时,例如xRequire(['url1','url2','url3','url4']),我无法让语法正常工作,你能详细解释一下吗? - aalvarado
@aalvarado 添加了。 - user6269864

0

这里有两个问题。首先,你需要从xRequire返回 Promise.all 调用,以便在你的xRequire(..).then中使用它:

return Promise.all(promisesList);

此外,当您使用.catch时,如果一个Promise最初被拒绝,它将进入catch块,执行其中的任何代码,并且然后Promise链将解析(而不是拒绝)为catch块返回的内容。如果您希望将错误上推到Promise链,将catch放置在您想要检测错误的链中的某个点上即可。

urls.forEach( (url, i) => { // (1)
  promisesList.push(
    fetch(url)
    .then(handleResponse)
    .then(data => console.log(data))
    // no catch here
  );
});

我建议将catch语句仅放在xRequire的调用者中,这样就能看到所有错误。你的xRequire函数可以简化为:
xRequire(['index.html', 'style.cds'])
  .then(nowInitialize)
  .catch(reason => 'One or more files failed to load' + reason)

function xRequire(files) {
  return Promise.all(
    urls.map(handleResponse)
  );
}

如果您希望xRequire的主体能够查看错误,但也希望将错误传递到Promise链中,请在xRequire内部的catch中抛出错误,以便它解析的Promise将被拒绝而不是解决:
function xRequire(files) {
  return Promise.all(
    urls.map(handleResponse)
  )
  .catch((err) => {
    console.log('There was an error: ' + err);
    throw err;
  })
}

在你的简化函数中,你能指出实际从服务器获取文件的位置吗? - aalvarado

0
我最终是这样解决的——到目前为止我发现唯一的怪癖是: files 参数总是需要是一个 数组,因此在调用函数时总是需要括号。
xRequire(['my-file'])
   .then(handle success)
   .catch(handle error);

async function xRequire(files) {
    let promises = [];
    let receivedData;

    //Iterate over files array and push results of fetch to promises array
    files.map(x => promises.push(fetch(x)));
    //populate receivedData array from promises array
    receivedData = await Promise.all(promises);
    //iterate over receivedData to handle each response accordingly
    return receivedData.map(x => handleResponse(x));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接