async function
should have await
expression JS-0116async
function without any await
expressions572 return await this.updateWorkItem(config, patchDoc);
573 }
574
575 async updateWorkItem(config, patchDoc) {576 return this.getWorkItem(config).then(async (workItem) => {577 if (!workItem) {578 if (!!config.ado.autoCreate) {579 log.warn(`Warning: cannot find work item (GitHub Issue #${config.issue.number}). Creating.`);580 workItem = await this.createWorkItem(config, true);581 } else {582 log.warn(`Warning: cannot find work item (GitHub Issue #${config.issue.number}). Canceling update.`);583 return 0;584 }585 }586587 let conn = this.getConnection(config);588 let client = await conn.getWorkItemTrackingApi();589 let result = null;590591 try {592 result = await client.updateWorkItem([], patchDoc, workItem.id, config.ado.project, false, config.ado.bypassRules);593 594 log.debug(result);595 log.info("Successfully updated work item:", result.id);596 597 return result;598 } catch (exc) {599 log.error("Error: failure updating work item.");600 log.error(exc);601 core.setFailed(exc);602 return -1;603 }604 });605 }606
607 async updateIssues(config) {
608 log.info("Updating issues...");
async
function without any await
expressions480 return await this.updateWorkItem(config, patchDoc);
481 }
482
483 async unlabelWorkItem(config) {484 log.info("Removing label from work item...");485486 return this.getWorkItem(config).then(async (workItem) => {487 if (!workItem) {488 log.warn(`Warning: cannot find work item (GitHub Issue #${config.issue.number}). Canceling update.`);489 return 0;490 }491492 let patchDoc = [493 {494 op: "replace",495 path: "/fields/System.Tags",496 value: workItem.fields["System.Tags"].replace(this.createLabels("", [config.label]), "")497 },498 {499 op: "add",500 path: "/fields/System.History",501 value: `GitHub issue #${config.issue.number}: <a href="${this.cleanUrl(config.issue.url)}" target="_new">${config.issue.title}</a> in <a href="${this.cleanUrl(config.issue.repository_url)}" target="_blank">${config.repository.full_name}</a> removal of label '${config.label.name}' by <a href="${config.issue.user.html_url}" target="_blank">${config.issue.user.login}</a>`502 }503 ];504505 return await this.updateWorkItem(config, patchDoc);506 });507 }508
509 async assignWorkItem(config) {
510 log.info("Assigning work item...");
async
function without any await
expressions251 }
252 }
253
254 async createWorkItem(config, skipQuery = false) {255 log.info("Creating work item...");256257 return this.getWorkItem(config, skipQuery).then(async (workItem) => {258 if (!!workItem) {259 log.warn(`Warning: work item (#${workItem.id}) already exists. Canceling creation.`);260 return 0;261 }262263 let converter = new showdown.Converter();264 const html = converter.makeHtml(config.issue.body);265 266 converter = null;267268 // create patch doc269 let patchDoc = [270 {271 op: "add",272 path: "/fields/System.Title",273 value: `GH #${config.issue.number}: ${config.issue.title}`274 },275 {276 op: "add",277 path: "/fields/System.Description",278 value: (!!html ? html : "")279 },280 {281 op: "add",282 path: "/fields/Microsoft.VSTS.TCM.ReproSteps",283 value: (!!html ? html : "")284 },285 {286 op: "add",287 path: "/fields/System.Tags",288 value: this.createLabels(`GitHub Issue;GitHub Repo: ${config.repository.full_name};`, config.issue.labels)289 },290 {291 op: "add",292 path: "/relations/-",293 value: {294 rel: "Hyperlink",295 url: this.cleanUrl(config.issue.url)296 }297 },298 {299 op: "add",300 path: "/fields/System.History",301 value: `GitHub issue #${config.issue.number}: <a href="${this.cleanUrl(config.issue.url)}" target="_new">${config.issue.title}</a> created in <a href="${this.cleanUrl(config.issue.repository_url)}" target="_blank">${config.repository.full_name}</a> by <a href="${config.issue.user.html_url}" target="_blank">${config.issue.user.login}</a>`302 }303 ]304305 // set assigned to306 if (!!config.ado.assignedTo) {307 patchDoc.push({308 op: "add",309 path: "/fields/System.AssignedTo",310 value: this.getAssignee(config, true)311 });312 }313314 // set area path if provided315 if (!!config.ado.areaPath) {316 patchDoc.push({317 op: "add",318 path: "/fields/System.AreaPath",319 value: config.ado.areaPath320 });321 }322323 // set iteration path if provided324 if (!!config.ado.iterationPath) {325 patchDoc.push({326 op: "add",327 path: "/fields/System.IterationPath",328 value: config.ado.iterationPath329 });330 }331332 // if bypass rules, set user name333 if (!!config.ado.bypassRules) {334 patchDoc.push({335 op: "add",336 path: "/fields/System.CreatedBy",337 value: config.issue.user.login338 });339 }340341 log.debug("Patch document:", patchDoc);342343 let conn = this.getConnection(config);344 let client = await conn.getWorkItemTrackingApi();345 let result = null;346347 try {348 result = await client.createWorkItem([], patchDoc, config.ado.project, config.ado.wit, false, config.ado.bypassRules);349350 if (result === null) {351 log.error("Error: failure creating work item.");352 log.error(`WIT may not be correct: ${config.ado.wit}`);353 core.setFailed();354 return -1;355 }356357 log.debug(result);358 log.info("Successfully created work item:", result.id);359360 return result;361 } catch (exc) {362 log.error("Error: failure creating work item.");363 log.error(exc);364 core.setFailed(exc);365 return -1;366 }367 });368 }369
370 async closeWorkItem(config) {
371 log.info("Closing work item...");
A function that does not contain any await
expressions should not be async
(except for some edge cases
in TypeScript which are discussed below).
Asynchronous functions in JavaScript behave differently than other functions in two important ways:
Promise
.await
operator inside them.Functions are made async
so that we can use the await
operator inside them.
Consider this example:
async function fetchData(processDataItem) {
const response = await fetch(DATA_URL);
const data = await response.json();
return data.map(processDataItem);
}
Asynchronous functions that don't use await
might be an unintentional result of refactoring.
Note: This issue ignores async generator functions.
Generators yield
rather than return
a value and async
generators might yield all the values of another async generator without ever actually needing to use await
.
In TypeScript, one might feel the need to make a function async
to comply with type signatures defined by an interface.
Ideally, the code should be refactored to get rid of such restrictions, but sometimes that isn't feasible
(For example, when we are implementing an interface defined in a 3rd party library like Next.js).
This situation can easily be circumvented by returning the value with a call to Promise.resolve
:
interface HasAsyncFunc {
getNum: () => Promise<number>
}
// Not recommended:
const o: HasAsyncFunc = {
async getNum() { return 1 }
}
// Recommended:
const o: HasAsyncFunc = {
// We only use `Promise.resolve` to adhere to the type
// of the surrounding object.
getNum() { return Promise.resolve(1) }
}
It is also advised to add a comment near the redundant promise to make the intent clear.
async function fetchData(): string {
// `readFileSync` is a synchronous function that blocks
// the main thread, and thus does not need to be `await`ed
return fs.readFileSync("data.txt", "utf-8");
}
performAction(async () => { console.log("no awaits in here") });
async function fetchDataAsync(): Promise<string> {
return await fs.readFile("data.txt", "utf-8")
}
performAction(async () => { await writeToFile(data) });
// Allow empty functions.
async function no_op() {}