对 Promises 和 async-await 感到困惑
Confused about Promises and async-await
我正在使用 GitHub api 创建一个应用程序,但我在使用异步函数时遇到了问题。我是使用异步的新手,所以非常感谢您的帮助。这是我到目前为止编写的代码:
const getFiles = async function(token, reponame) {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = gh.getRepo(reponame[0], reponame[1]);
let head = new Headers();
head.append("Authorization: ", "token " + token);
const getContents = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) rej(err);
else return files;
}).then(files => {
let promises = [
files.map(
file =>
new Promise(res => {
fetch(file.downloadURL).then(body => {
res(body.text);
});
})
)
];
const retFiles = [];
await Promise.all(promises.map(promise => retFiles.push(promise)));
res(retFiles)
});
});
return getContents;
};
我得到的错误是在我使用 await 的行中意外的保留字。提前致谢
所以我要在这里做一些假设,所以如果我错了请纠正我,我会修正它们。希望这样做,我可以帮助澄清你的理解。
考虑 async/await
的一种简单方法是替换对 .then(callback)
的需求。如果在 async function
.
中,我更喜欢使用 await
const getFiles = async function(token, reponame) {
try {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
// edit: I removed await from the line below as your original code
// did not treat it as a promise
const repo = gh.getRepo(reponame[0], reponame[1]);
// unused code from your post
let head = new Headers();
head.append("Authorization: ", "token " + token);
// the await below assumes that repo.getContents
// will return a promise if a callback is not provided
const files = await repo.getContents(null, "content", true);
// updating the code below so that the file requests run in parallel.
// this means that all requests are going to fire off basically at once
// as each fetch is called
const fileRequests = files.map(file => fetch(file.downloadURL))
// you wont know which failed with the below.
const results = (await Promise.all(fileRequests)).map(res => res.text)
return results
} catch (err) {
// handle error or..
throw err;
}
};
此代码未经测试。我没有使用 github 的 api 所以我最好猜测每个调用在做什么。如果 gh.getRepo
或 repo.getContents
没有 return 承诺,则需要进行一些调整。
如果您正在使用的 github 库不会 return 如果未提供回调则承诺:
const getFiles = async function(token, reponame) {
try {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = await gh.getRepo(reponame[0], reponame[1]);
let head = new Headers();
head.append("Authorization: ", "token " + token);
const getContents = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) {
return rej(err);
}
res(files)
})
})
const fileRequests = (await getContents).map(file => fetch(file.downloadURL))
return (await Promise.all(fileRequests)).map(res => res.text)
} catch (err) {
// handle error or..
throw err;
}
};
这是一个使用 async/await 的示例,它使用新的承诺来承诺回调:
const content = document.getElementById("content")
const result = document.getElementById("result")
async function example(){
content.innerHTML = 'before promise';
const getContents = new Promise((res, rej) => {
setTimeout(()=> {
res('done')
}, 1000)
})
const res = await getContents
content.innerHTML = res
return res
}
example().then((res)=> {
result.innerHTML = `I finished with <em>${res}</em> as a result`
})
<div id="content"></div>
<div id="result"></div>
这就是为什么我最初用等待每个请求的 for...of 循环编写答案的原因。所有的promise基本上都是一次性执行的:
const content = document.getElementById("content")
const result = document.getElementById("result")
async function example() {
const promises = []
content.innerHTML = 'before for loop. each promise updates the content as it finishes.'
for(let i = 0; i < 20; i++){
const promise = new Promise((resolve, reject) => {
setTimeout(()=> {
content.innerHTML = `current value of i: ${i}`
resolve(i)
}, 1000)
})
promises.push(promise)
}
const results = await Promise.all(promises)
return results
}
example().then(res => result.innerHTML= res.join(', '))
content:
<div id="content"></div>
result:
<div id="result"></div>
我发现这是修改后的代码:
const getFiles = async function(token, reponame) {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = gh.getRepo(reponame[0], reponame[1]);
let files = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) rej(err);
else res(files);
});
});
let content = new Promise(res => {
files.then(files => {
const promises = files.reduce((result, file) => {
if (file.name.endsWith(".md")) {
result.push(
new Promise((res, rej) => {
repo.getContents(null, file.path, true, (err, content) => {
if (err) rej(err);
else
res({
path: file.path,
content: content
});
});
})
);
}
return result;
}, []);
console.log(promises);
res(
Promise.all(
promises.map(promise =>
promise.then(file => {
return file;
})
)
)
);
});
});
return await content;
};
我仍然不知道这是否是 "right" 的方法,但它确实有效。
await
关键字只能与 async
函数一起使用。如果您注意到,您的 await Promise.all(promises.map(promise => retFiles.push(promise)));
位于在 .then
中传递 files
参数的函数内。只需使该函数 async
和 await
将在作用域内工作。试试下面的代码。
const getFiles = async function(token, reponame) {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = gh.getRepo(reponame[0], reponame[1]);
let head = new Headers();
head.append("Authorization: ", "token " + token);
const getContents = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) rej(err);
else return files;
}).then( async (files) => {
let promises = [
files.map(
file =>
new Promise(res => {
fetch(file.downloadURL).then(body => {
res(body.text);
});
})
)
];
const retFiles = [];
await Promise.all(promises.map(promise => retFiles.push(promise)));
res(retFiles)
});
});
return getContents;
};
这里有很多问题。代码很复杂;不需要所有这些承诺以及间接层和嵌套来实现您的需要。
您尝试做的模式很常见:
- 请求获取实体列表(文件、用户、URL...)。
- 对于列表中 return 的每个实体,再次请求获取更多信息。
- Return 结果作为承诺(必须是承诺,因为
async
函数只能 return 承诺)。
做到这一点的方法是将问题分成几个阶段。在大多数情况下,使用 await
和 async
关键字而不是 .then
。为了使示例可重现,我将使用一个场景,我们希望获取在 GitHub 上创建的最新 n
要点的用户配置文件——这基本上等同于您正在做的事情,我留给你去推断。
第一步是获取实体的初始列表(最近创建的要点):
const res = await fetch("https://api.github.com/gists/public");
const gists = await res.json();
接下来,对于来自 0..n
的要点数组中的每个要点,我们需要触发一个请求。重要的是要确保我们没有使用 await
:
在此处序列化任何内容
const requests = gists.slice(0, n).map(gist =>
fetch(`https://api.github.com/users/${gist.owner.login}`)
);
现在所有请求都在进行中,我们需要等到它们完成。这就是 Promise.all
的用武之地:
const responses = await Promise.all(requests);
最后一步是从每个响应中获取 JSON,这需要另一个 Promise.all
:
return await Promise.all(responses.map(e => e.json()));
这是我们的最终结果,可以 returned。这是代码:
const getRecentGistCreators = async (n=1) => {
try {
const res = await fetch("https://api.github.com/gists/public");
const gists = await res.json();
const requests = gists.slice(0, n).map(gist =>
fetch(`https://api.github.com/users/${gist.owner.login}`)
);
const responses = await Promise.all(requests);
return await Promise.all(responses.map(e => e.json()));
}
catch (err) {
throw err;
}
};
(async () => {
try {
for (const user of await getRecentGistCreators(5)) {
const elem = document.createElement("div");
elem.textContent = user.name;
document.body.appendChild(elem);
}
}
catch (err) {
throw err;
}
})();
作为改进说明,最好在使用额外承诺完成请求的同一阶段请求 JSON,但为了简单起见,我们将分两个连续步骤进行.作为设计要点,将负担过重的 getRecentGistCreators
也分成两个单独的步骤可能会很好。
我正在使用 GitHub api 创建一个应用程序,但我在使用异步函数时遇到了问题。我是使用异步的新手,所以非常感谢您的帮助。这是我到目前为止编写的代码:
const getFiles = async function(token, reponame) {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = gh.getRepo(reponame[0], reponame[1]);
let head = new Headers();
head.append("Authorization: ", "token " + token);
const getContents = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) rej(err);
else return files;
}).then(files => {
let promises = [
files.map(
file =>
new Promise(res => {
fetch(file.downloadURL).then(body => {
res(body.text);
});
})
)
];
const retFiles = [];
await Promise.all(promises.map(promise => retFiles.push(promise)));
res(retFiles)
});
});
return getContents;
};
我得到的错误是在我使用 await 的行中意外的保留字。提前致谢
所以我要在这里做一些假设,所以如果我错了请纠正我,我会修正它们。希望这样做,我可以帮助澄清你的理解。
考虑 async/await
的一种简单方法是替换对 .then(callback)
的需求。如果在 async function
.
await
const getFiles = async function(token, reponame) {
try {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
// edit: I removed await from the line below as your original code
// did not treat it as a promise
const repo = gh.getRepo(reponame[0], reponame[1]);
// unused code from your post
let head = new Headers();
head.append("Authorization: ", "token " + token);
// the await below assumes that repo.getContents
// will return a promise if a callback is not provided
const files = await repo.getContents(null, "content", true);
// updating the code below so that the file requests run in parallel.
// this means that all requests are going to fire off basically at once
// as each fetch is called
const fileRequests = files.map(file => fetch(file.downloadURL))
// you wont know which failed with the below.
const results = (await Promise.all(fileRequests)).map(res => res.text)
return results
} catch (err) {
// handle error or..
throw err;
}
};
此代码未经测试。我没有使用 github 的 api 所以我最好猜测每个调用在做什么。如果 gh.getRepo
或 repo.getContents
没有 return 承诺,则需要进行一些调整。
如果您正在使用的 github 库不会 return 如果未提供回调则承诺:
const getFiles = async function(token, reponame) {
try {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = await gh.getRepo(reponame[0], reponame[1]);
let head = new Headers();
head.append("Authorization: ", "token " + token);
const getContents = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) {
return rej(err);
}
res(files)
})
})
const fileRequests = (await getContents).map(file => fetch(file.downloadURL))
return (await Promise.all(fileRequests)).map(res => res.text)
} catch (err) {
// handle error or..
throw err;
}
};
这是一个使用 async/await 的示例,它使用新的承诺来承诺回调:
const content = document.getElementById("content")
const result = document.getElementById("result")
async function example(){
content.innerHTML = 'before promise';
const getContents = new Promise((res, rej) => {
setTimeout(()=> {
res('done')
}, 1000)
})
const res = await getContents
content.innerHTML = res
return res
}
example().then((res)=> {
result.innerHTML = `I finished with <em>${res}</em> as a result`
})
<div id="content"></div>
<div id="result"></div>
这就是为什么我最初用等待每个请求的 for...of 循环编写答案的原因。所有的promise基本上都是一次性执行的:
const content = document.getElementById("content")
const result = document.getElementById("result")
async function example() {
const promises = []
content.innerHTML = 'before for loop. each promise updates the content as it finishes.'
for(let i = 0; i < 20; i++){
const promise = new Promise((resolve, reject) => {
setTimeout(()=> {
content.innerHTML = `current value of i: ${i}`
resolve(i)
}, 1000)
})
promises.push(promise)
}
const results = await Promise.all(promises)
return results
}
example().then(res => result.innerHTML= res.join(', '))
content:
<div id="content"></div>
result:
<div id="result"></div>
我发现这是修改后的代码:
const getFiles = async function(token, reponame) {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = gh.getRepo(reponame[0], reponame[1]);
let files = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) rej(err);
else res(files);
});
});
let content = new Promise(res => {
files.then(files => {
const promises = files.reduce((result, file) => {
if (file.name.endsWith(".md")) {
result.push(
new Promise((res, rej) => {
repo.getContents(null, file.path, true, (err, content) => {
if (err) rej(err);
else
res({
path: file.path,
content: content
});
});
})
);
}
return result;
}, []);
console.log(promises);
res(
Promise.all(
promises.map(promise =>
promise.then(file => {
return file;
})
)
)
);
});
});
return await content;
};
我仍然不知道这是否是 "right" 的方法,但它确实有效。
await
关键字只能与 async
函数一起使用。如果您注意到,您的 await Promise.all(promises.map(promise => retFiles.push(promise)));
位于在 .then
中传递 files
参数的函数内。只需使该函数 async
和 await
将在作用域内工作。试试下面的代码。
const getFiles = async function(token, reponame) {
var gh = new GitHub({
token: token
});
reponame = reponame.split("/");
const repo = gh.getRepo(reponame[0], reponame[1]);
let head = new Headers();
head.append("Authorization: ", "token " + token);
const getContents = new Promise((res, rej) => {
repo.getContents(null, "content", true, (err, files) => {
if (err) rej(err);
else return files;
}).then( async (files) => {
let promises = [
files.map(
file =>
new Promise(res => {
fetch(file.downloadURL).then(body => {
res(body.text);
});
})
)
];
const retFiles = [];
await Promise.all(promises.map(promise => retFiles.push(promise)));
res(retFiles)
});
});
return getContents;
};
这里有很多问题。代码很复杂;不需要所有这些承诺以及间接层和嵌套来实现您的需要。
您尝试做的模式很常见:
- 请求获取实体列表(文件、用户、URL...)。
- 对于列表中 return 的每个实体,再次请求获取更多信息。
- Return 结果作为承诺(必须是承诺,因为
async
函数只能 return 承诺)。
做到这一点的方法是将问题分成几个阶段。在大多数情况下,使用 await
和 async
关键字而不是 .then
。为了使示例可重现,我将使用一个场景,我们希望获取在 GitHub 上创建的最新 n
要点的用户配置文件——这基本上等同于您正在做的事情,我留给你去推断。
第一步是获取实体的初始列表(最近创建的要点):
const res = await fetch("https://api.github.com/gists/public");
const gists = await res.json();
接下来,对于来自 0..n
的要点数组中的每个要点,我们需要触发一个请求。重要的是要确保我们没有使用 await
:
const requests = gists.slice(0, n).map(gist =>
fetch(`https://api.github.com/users/${gist.owner.login}`)
);
现在所有请求都在进行中,我们需要等到它们完成。这就是 Promise.all
的用武之地:
const responses = await Promise.all(requests);
最后一步是从每个响应中获取 JSON,这需要另一个 Promise.all
:
return await Promise.all(responses.map(e => e.json()));
这是我们的最终结果,可以 returned。这是代码:
const getRecentGistCreators = async (n=1) => {
try {
const res = await fetch("https://api.github.com/gists/public");
const gists = await res.json();
const requests = gists.slice(0, n).map(gist =>
fetch(`https://api.github.com/users/${gist.owner.login}`)
);
const responses = await Promise.all(requests);
return await Promise.all(responses.map(e => e.json()));
}
catch (err) {
throw err;
}
};
(async () => {
try {
for (const user of await getRecentGistCreators(5)) {
const elem = document.createElement("div");
elem.textContent = user.name;
document.body.appendChild(elem);
}
}
catch (err) {
throw err;
}
})();
作为改进说明,最好在使用额外承诺完成请求的同一阶段请求 JSON,但为了简单起见,我们将分两个连续步骤进行.作为设计要点,将负担过重的 getRecentGistCreators
也分成两个单独的步骤可能会很好。