async await

介绍

在web开发中,我们经常需要处理异步操作------这些操作可以在执行其他任务的同时等待完成。我们会向网络、数据库或类似的操作发送请求。JS是非阻塞的,不会停止运行直到代码执行完,而是使用更高效的事件循环,在等待异步任务完成时转而去执行其他任务

事件循环(Event loop):js处理异步操作的机制,负责协调同步任务和异步任务的执行顺序

起初,js使用回调函数来进行一步操作。回调函数的问题就在于,它们导致代码嵌套过于复杂,使代码难以阅读、调试、和拓展。随着es6的发布,js引入了原生promise,使我们可以编写更易读的代码,js不算发展,在es8中引入了一种新的语法来处理异步操作:async...await

async...await语法使我们能够编写的异步代码看起来更像传统的同步命令式程序

async...await 语法实际上是一种语法糖(syntactic sugar) ------它并未引入新的功能,而是为 Promise 和生成器(generators)提供了一种新的使用方式,而这些功能本身早已内置于 JavaScript 语言中。尽管如此,async...await 显著提升了代码的可读性可扩展性

使用回调函数实现异步:

js 复制代码
fs.readFile('./file.txt', 'utf-8', (err, data) => {
  if (err) throw err;
  let firstSentence = data;
  fs.readFile('./file2.txt',  'utf-8', (err, data) => {
    if (err) throw err;
    let secondSentence = data;
    console.log(firstSentence, secondSentence);
  });
});

用promise实现异步:

js 复制代码
let firstSentence;
promisifiedReadfile('./file.txt', 'utf-8')
  .then((data) => {
    firstSentence = data;
    return promisifiedReadfile('./file2.txt', 'utf-8');
  })
  .then((data) => {
    let secondSentence = data;
    console.log(firstSentence, secondSentence)
  })
  .catch((err) => {console.log(err)});

用async......await实现异步:

js 复制代码
async function readFiles() {
  let firstSentence = await promisifiedReadfile('./file.txt', 'utf-8');
  let secondSentence = await promisifiedReadfile('./file2.txt', 'utf-8');
  console.log(firstSentence, secondSentence);
}

async关键字

async用于处理异步操作的函数,我们将异步逻辑封装在一个函数中,并在前面加上async关键字,之后调用函数:

js 复制代码
async function myFunc() {
  // Function body here
};

myFunc();

我们通过使用async函数来声明,但是我们也可以用async来创建函数表达式

js 复制代码
const myFunc = async () => {
  // Function body here
};

myFunc();

async函数通常会返回一个promise,这意味着我们可以在async上使用传统的promise语法类似then,catch。一个async函数会返回三种状态:

  • 如果没有任何返回值,则该函数会返回一个promise和一个undefined解析值
  • 如果async返回的是一个非promise的值,它将返回一个解析为该值的promise

async 总是返回一个promise,无论函数内部返回的是一个普通值还是一个promise

  1. 当返回非promise时:
js 复制代码
async function example() {
  return 42;
}

example().then(console.log); // 输出:42
  • example作为async函数,返回的是一个promise,即使return的值(42)本身就是一个普通值
  • JavaScript 自动将 42 包装成 Promise.resolve(42) ,所以我们可以用 .then() 处理它。
  1. 返回promise值
js 复制代码
async function example2() {
  return Promise.resolve("Hello");
}

example2().then(console.log); // 输出:Hello
  • example2() 内部返回了一个 Promise,不会再额外包装,而是直接返回这个 Promise

例子:

js 复制代码
function withConstructor(num){
  return new Promise((resolve, reject) => {
    if (num === 0){
      resolve('zero');
    } else {
      resolve('not zero');
    }
  });
}

withConstructor(0)
  .then((resolveValue) => {
  console.log(` withConstructor(0) returned a promise which resolved to: ${resolveValue}.`);
});

// Write your code below:
const withAsync = async(num) => {
  if(num === 0){
    return 'zero';
  }else{
    return 'not zero';
  }
}

withAsync(0)
  .then((resolveValue) => {
  console.log(` withConstructor(0) returned a promise which resolved to: ${resolveValue}.`);
});

第一个函数通过new promise的方式返回promise,第二个函数用async自动包装promise的方式返回promise

await操作符

在上一个练习中,我们理解了async关键字,但这个关键字很少单独使用,大多数时候和函数体内的await关键字一起使用

await关键字只可以用在async函数内部,await是一个操作符:它会返回promise解析的值。由于promise解析的时间是不确定的,await会暂停(或阻塞)async函数的执行,直到指定的promise被解析

在大多数情况下,我们处理的是从函数返回的promise。通常,这些函数来自某个库。

在async函数内部,我们可以使用await来等待它返回的promise解析完成。在下面的例子中,myPromise()是一个会返回promise并解析成字符串"I am resolved now!"的函数

js 复制代码
async function asyncFuncExample(){
  let resolvedValue = await myPromise();
  console.log(resolvedValue);
}

asyncFuncExample(); // Prints: I am resolved now!

例如:

js 复制代码
const brainstormDinner = require('./library.js');


// Native promise version:
function nativePromiseDinner() {
  brainstormDinner().then((meal) => {
	  console.log(`I'm going to make ${meal} for dinner.`);
  });
}


// async/await version:
async function announceDinner() {
  // Write your code below:
  let meal = await brainstormDinner();

  console.log(`I'm going to make ${meal} for dinner.`);
  
}

announceDinner();

编写异步函数

我们已经知道await关键字会暂停执行async函数直到promise不再是pending。所以不要忘记await关键字,如果没有加上的话,我们的函数仍然会运行,只是不会得到预期的结果

例如:

js 复制代码
let myPromise = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Yay, I resolved!')
    }, 1000);
  });
}

现在我们写两个函数去调用promise

js 复制代码
async function noAwait() {
 let value = myPromise();
 console.log(value);
}

async function yesAwait() {
 let value = await myPromise();
 console.log(value);
}

noAwait(); // Prints: Promise { <pending> }
yesAwait(); // Prints: Yay, I resolved!

第一个异步函数没有await,第二个异步函数带了await

没有await的异步函数中,函数执行没有暂停,所以会打印出pending,证明console.log在promise变为settle前就已经执行

记住await操作返回promise的解析值 ,当在yesAwait函数中正确使用await时,变量value被赋值为变量 value 被赋值为 myPromise() 解析后的值,而在 noAwait() 中,value 被赋值为 Promise 对象本身。

promise的解析值:当promise进入fulfilled状态后返回的具体值,即是resolve(value) 传递的 value。 如果 Promise 进入 rejected 状态(即 reject(error) 被调用),那么它没有解析值,而是有一个 拒绝的原因(rejection reason).catch() 处理 rejected 状态,并输出错误信息。

处理独立的promise

async await真正的魅力在于当我们一系列相互依赖的异步操作时,它能使代码更清晰易读。例如,我们可能要根据数据库查询的结果发起网络请求,在这种情况下,我们必须等到数据库查询完成后,才能进行网络请求

如果是原生promise,我们需要通过then函数链式调用来实现,then会确保返回正确的:

js 复制代码
function nativePromiseVersion() {
  returnsFirstPromise()
    .then((firstValue) => {
      console.log(firstValue);
      return returnsSecondPromise(firstValue);
    })
   .then((secondValue) => {
      console.log(secondValue);
    });
}

但如果我们用async去实现:

js 复制代码
async function asyncAwaitVersion() {
  let firstValue = await returnsFirstPromise();
  console.log(firstValue);
  let secondValue = await returnsSecondPromise(firstValue);
  console.log(secondValue);
}

还有类似的例子,可以使异步操作按序依次运行:

js 复制代码
const {shopForBeans, soakTheBeans, cookTheBeans} = require('./library.js');

// Write your code below:
const makeBeans = async() => {
  let type = await shopForBeans();
  let isSoft = await soakTheBeans(type);
  let dinner = await cookTheBeans(isSoft);

  console.log(dinner);
}

makeBeans();

处理错误

当catch用于较长的promise链时,并不会指示错误发生在链中的具体位置,这会使调试变得困难

和async await类似,我们使用try catch来处理错误。通过使用这个语法,我们不仅可以像处理同步代码一样处理错误,还可以捕获同步和异步错误,这使得调试变得更容易

js 复制代码
async function usingTryCatch() {
 try {
   let resolveValue = await asyncFunction('thing that will fail');
   let secondValue = await secondAsyncFunction(resolveValue);
 } catch (err) {
   // Catches any errors in the try block
   console.log(err);
 }
}

usingTryCatch();

记住,由于async函数返回的是promise,我们仍然可以使用原生promise的catch方法来处理async函数错误

js 复制代码
async function usingPromiseCatch() {
   let resolveValue = await asyncFunction('thing that will fail');
}

let rejectedPromise = usingPromiseCatch();
rejectedPromise.catch((rejectValue) => {
console.log(rejectValue);
})

这有时会在全局作用域中使用,以捕获复杂代码中的最终错误。

js 复制代码
const cookBeanSouffle = require('./library.js');

// Write your code below:
const hostDinnerParty = async() => {
  try{

    let beanSouffle = await cookBeanSouffle();

    console.log(`${beanSouffle} is served!`)
  }catch(error){
    console.log(error);
    console.log('Ordering a pizza!');
  }
}

hostDinnerParty();

处理依赖promise

我们都记得await会停止异步函数的执行,这允许我们方便地书写处理独立promise的异步代码,但是当我们的异步中包含多个promise,且是相互依赖的,应该如何去做?

js 复制代码
async function waiting() {
 const firstValue = await firstAsyncThing();
 const secondValue = await secondAsyncThing();
 console.log(firstValue, secondValue);
}

async function concurrent() {
 const firstPromise = firstAsyncThing();
 const secondPromise = secondAsyncThing();
console.log(await firstPromise, await secondPromise);
}

在waiting函数中,我们暂停了函数直到第一个promise成为resolved,然后我们构建第二个函数,一旦resolved,我们就把两个解析的数值打印到控制台。

在concurrent()函数中,两个promise都没有使用await,然后我们等待它们的resolved,并将结果打印到控制台

在我们的concurrent()函数中,两个promise的异步操作可以同时运行,如果可能的话,我们希望尽早开始每个异步操作。在我们的异步函数中,我们仍然应该利用并发性,即同时执行异步函数的能力

注意:如果我们有多个真正相互独立的promise并希望它们能够并行执行,我们必须使用单独的then函数,并且避免使用await造成阻塞

js 复制代码
let {cookBeans, steamBroccoli, cookRice, bakeChicken} = require('./library.js');

// Write your code below:
const serveDinner = async() => {
  const vegetablePromise =  steamBroccoli();

  const starchPromise =  cookRice();

  const proteinPromise =  bakeChicken();

  const sidePromise = cookBeans();

  console.log(`Dinner is served. We're having ${await vegetablePromise}, ${await starchPromise}, ${await proteinPromise}, and ${await sidePromise}.`)
}

serveDinner();

可以在字符串构造中使用await运算符

await promise.all()

当我们有多个promise时,另一个提高并发性的方法是使用await和promise.all

promise.all可以将promise数组作为参数,并且会返回单个promise。如果所有的promise都为resolve,则返回的promise也为resolve,且解析值为由每个promise的解析值为元素所构成的数组

js 复制代码
async function asyncPromAll() {
  const resultArray = await Promise.all([asyncTask1(), asyncTask2(), asyncTask3(), asyncTask4()]);
  for (let i = 0; i<resultArray.length; i++){
    console.log(resultArray[i]); 
  }
}

在上面的例子中,我们await了promis.all的结果。

promise.all允许我们利用异步特性------四个异步任务可以并发执行

promise.all还有一个快速失败 的优点,这意味着一旦其中任何一个promise被拒绝,它不会等待其他异步操作完成,而是立即返回一个同样的rejected。就像使用原生 Promise 时一样,如果多个异步任务都是必需的,并且它们之间不需要相互等待才能执行,那么 Promise.all() 是一个不错的选择。

js 复制代码
let {cookBeans, steamBroccoli, cookRice, bakeChicken} = require('./library.js');

// Write your code below:



const serveDinnerAgain = async() => {

  let foodArray = await Promise.all([steamBroccoli(), cookRice(),bakeChicken(), cookBeans()]);

let vegetable = foodArray[0];
let starch =  foodArray[1];
let protein =  foodArray[2];
let side =  foodArray[3];
  
console.log(`Dinner is served. We're having ${vegetable}, ${starch}, ${protein}, and ${side}.`);

}
相关推荐
烛阴22 分钟前
秒懂 JSON:JavaScript JSON 方法详解,让你轻松驾驭数据交互!
前端·javascript
拉不动的猪30 分钟前
刷刷题31(vue实际项目问题)
前端·javascript·面试
zeijiershuai32 分钟前
Ajax-入门、axios请求方式、async、await、Vue生命周期
前端·javascript·ajax
恋猫de小郭34 分钟前
Flutter 小技巧之通过 MediaQuery 优化 App 性能
android·前端·flutter
只会写Bug的程序员43 分钟前
面试之《webpack从输入到输出经历了什么》
前端·面试·webpack
拉不动的猪1 小时前
刷刷题30(vue3常规面试题)
前端·javascript·面试
狂炫一碗大米饭1 小时前
面试小题:写一个函数实现将输入的数组按指定类型过滤
前端·javascript·面试
最胖的小仙女1 小时前
通过动态获取后端数据判断输入的值打小
开发语言·前端·javascript
yzhSWJ1 小时前
Vue 3 中,将静态资源(如图片)转换为 URL
前端·javascript·vue.js
Moment1 小时前
🏞 JavaScript 提取 PDF、Word 文档图片,非常简单,别再头大了!💯💯💯
前端·javascript·react.js