Javascript中的await与async与promise存在什么关系?

一、初识await与async

初学到async与await时只是停留在表面,明白await是用来等待函数执行的,async是用来声明一个函数为异步函数的,并且只知道只有声明了async的函数,里面才可以用await。

现在想起来,有一点没错,这两个的确是成对出现的。但是申明了async的函数是异步函数?这个是错的,申明了async的函数还是同步函数,因为我们知道,如果是异步函数,那么他的执行顺序肯定在同步函数之后:

async function fun1(){
  console.log("我像是异步函数吗?")
}

function fun2(){
  console.log("我是同步函数")
}
fun1()
fun2()
console.log("666")

 所以申明async与异步函数并没有直接关系,想要知道为什么,继续看下面:

先说一下async的声明语法:

语法就是:

 如果没有声明async将会报错:

二、async有什么作用

它的全名是:await asynchronous(等待异步)

1.先来看看例子:

例子1:

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);
c:var	est> node --harmony_async_await .
Promise { 'hello async' }

打印出来发现,申明了等待异步的函数,并不是直接返回一个我们想要的值,而是一个promise对象。原来是promise对象,那么就可以使用then链语法,一直then下去:

例子1.2

async function async1() {
  console.log("async1 start");
  console.log("async1 end");
}
let b =async1()
console.log(b)

 如果async没有返回值,则直接返回一个undefined,且外面是一个promise包着

例子2:

先来看看第一个普通使用promise的例子:

function conductStr(str){
  return new Promise((resolve)=>{
    resolve(str.substr(5).split('>'))
  })
}

//下面代码都是废话,主要看上面绿色区域
let my_str = conductStr('当前位置:初三网 >肇庆中考 > 肇庆地区高中>肇庆市第四中学')
my_str.then((response)=>{
  console.log(response)
}).catch(err=>{
  console.log(err)
})
 

再来看看处理同一操作的async可以怎么写:

async function conductStr(str){
    return ( str.substr(5).split('>'))

}

//同样下面是废话
let my_str = conductStr('当前位置:初三网 >肇庆中考 > 肇庆地区高中>肇庆市第四中学')
my_str.then((response)=>{
 console.log(response)
}).catch(err=>{ console.log(err) })

同样输出结果:

 我们可以看到,async申明的函数会把返回结果直接实例化一个new promise,然后通过resolve把参数传出去。

所以总结一下:申明async函数的返回值会默认返回一个promise,这个返回值可以使用then链语法,一直写下去。

带 async 关键字的函数,它使得你的函数的返回值必定是 promise 对象

也就是

如果async关键字函数返回的不是promise,会自动用Promise.resolve()包装

如果async关键字函数显式地返回promise(即你自定义的promise),那就以你返回的promise为准

三、await有什么作用

await是用来等待后面的表达式执行完毕,才能继续执行下去,await后面的表达式可以是一个函数,也可以是一个普通表达式子

看看例子:

(1)异步方法1,2都用了await的情况:(注意,我所说的异步方法是指里面的settimeout,而不是外面的function,后面也是这样,而等待异步方法是指async函数,下面就不在继续提醒了。)

function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
    return resolve("我是异步方法1");
  }, 1000);
})
}

function Async2() {
  return new Promise((resolve)=>{
    setTimeout(() => {
      return resolve("我是异步方法二");
    }, 10); 
  })
}

async function test() {
  const v1 = await Async1();
  const v2 = await Async2();
  console.log(v1, v2);
}

test();

可以看到直接输出resolve的参数 。

(2)异步方法1不用await,异步方法2用await

function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
    return resolve("我是异步方法1");
  }, 1000);
})
}

function Async2() {
  return new Promise((resolve)=>{
    setTimeout(() => {
      return resolve("我是异步方法二");
    }, 10); 
  })
}

async function test() {
  const v1 = Async1();
  const v2 = await Async2();
  console.log(v1, v2);
}

test();

 可以看到没有await,直接返回一个pending,那么是否await来等待一个具体值呢?这个我们等下再考虑。

(3)await是否可以等待其它函数,或者表达式:

function normal(){
  let a=1;

  return"我是同步函数"
}
async function Async0(){
  return"我是异步函数零"
}
function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
    resolve("我是异步方法一")
  }, 1000);
})
}

function Async2() {
  return new Promise((resolve)=>{
    setTimeout(() => {
      return resolve("我是异步方法二");
    }, 10); 
  })
}

async function test() {
  const num = await (1+1);
  const n = await normal()
  const v0 = await Async0();
  const v1 = await Async1();
  const v2 = await Async2();
  console.log(n,v0,v1, v2,num);
}

test();

 问题答案如上面演示,答案是可以的。

(4)同步方法没有返回值时,await会怎么处理

function normal(){
  let a=1;

}

function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
    resolve("我是异步方法一")
  }, 1000);
})
}

function Async2() {
  return new Promise((resolve)=>{
    setTimeout(() => {
      return resolve("我是异步方法二");
    }, 10); 
  })
}

async function test() {
  const n = await normal()
  const v1 = await Async1();
  const v2 = await Async2();
  console.log(n,v1,v2)
}

 当同步没有返回值时,await默认返回undefined,因为await会认为同步函数执行完了,然后await知道同步函数没有返回值。

(5)异步方法没有返回值时,await怎么操作

function normal(){
  let a=1;

}

function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
    resolve("我是异步方法一")
  }, 1000);
})
}

function Async2() {
  return new Promise((resolve)=>{
    setTimeout(() => {
      // return resolve("我是异步方法二");把异步方法二返回值注释掉
    }, 10); 
  })
}

async function test() {
  const n = await normal()
  const v1 = await Async1();
  const v2 = await Async2();
  console.log(n,v1,v2)
}

test();

 此时await会直接退出,其实我觉得应该结果会直接阻塞才对,因为异步代码没有返回值,await就会一直等待,await一直等待,test函数就会奔溃,最后退出才对;因为await可以判断同步代码没有返回值,但是异步代码返回值什么时候返回,它是无法知道的。

说明白一点,我们知道通常我们使用promise是用来包着一个异步操作的,等待异步操作完成,才把结果resolve出去,但是这个异步操作什么时候完成,没有人会知道,就像我们在像网络世界发起请求时,如果服务器响应速度快,我们就很快得到数据,相反,要是服务器很慢,我们就很晚才拿到数据,但是具体请求数据要多久才能拿到数据我们是不知道的,可能1ms,可能3s,亦或是1分钟,一天,十天,半个月,一年都有可能,只不过我们的日常应用都做了处理,如果超过一定时间没有请求得到数据,那么浏览器或者网页会返回404,或者其他通知用户请求数据失败的消息,以优化用户体验。

回到上面那个例子,settimeout就是一个异步操作,里面resolve就是包含着我们的数据,如果我们一直不resolve,外面就一直拿不到数据,所以我自己觉得要是await在超出一段时间拿不到数据,它就认为请求失败,整个async函数全部退出,即使最前面的几个await拿到值了,它也会导致程序直接退出。这里说的不太准确,应该说阻塞才退,其实前面的await已经拿到值了,只不过后面的输出函数被阻塞了,输出不了。

(6)声明了async的函数,有用await与不用await的区别:

不用await:

async function async1() {
  console.log( 'async1 start' )
   async2()
  console.log( 'async1 end' )
}
async function async2() {
  console.log( 'async2' )
}
async1()
console.log( 'script start' )

执行顺序与普通函数没什么区别:

 用了await:

async function async1() {
  console.log( 'async1 start' )
  await async2()
  console.log( 'async1 end' )
}
async function async2() {
  console.log( 'async2' )
}
async1()
console.log( 'script start' )

 所以对于await来说,等到的对象分2个情况

  • 不是promise对象
  • 是promise对象

如果不是 promise , await会阻塞async函数内部的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果

如果它等到的是一个 promise 对象,await 也会阻塞async内部后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

有的人可能会疑惑,为什么阻塞了,那个async2还是打印出来了,这其实是代码执行顺序是从由到左执行的,先执行了asyn2(),然后发现有await之后,再阻塞内部,然后跳到外部先执行。

看到这里的同学可以去网上查查js的事件循环,然后查一下之前今日头条的那道面试题,不过我看到那个面试题的答案有两种,有个博主是写的很详细,而且解释起来也令我信服,但是执行结果和他给出的答案的有点出入,我也不知道怎么回事。感兴趣的可以看看:https://www.cnblogs.com/fundebug/p/10095355.html#4837719

四、总结

(1)async用来申明一个函数是等待异步函数,只有申明了async的函数,内部才可以使用await;

(2)申明了async函数,那么返回值会是一个promise,返回的类型语法格式:promise{‘参数’},但是它仍属于同步方法。

(3)await是用来等待一个表达式的,这个表达式可以是任何一个表达式,当返回的为非promise表达式时,无论await等到的是什么,等待异步函数最终会全部执行。当等到的是promise,而且这个promise没有返回值时,它就会导致等待异步函数阻塞,从await开始一直阻塞,然后退出,此时程序会输出await之前的执行结果。(就像promise的中文意思一样,承诺,就是它承诺了会返回一个数据,但是结果它没有返回,如此不诚信的行为将会导致整个函数异常)

(4)当await内部阻塞时,会先去外面把其他同步函数执行完,再返回到当前阻塞代码继续执行。

 附上我画的声明了async的函数与promise函数与异步请求函数的关系:

 一个async可以包含多个promise,使用await达到多个promise.then()同步的效果:

 function fun1(){
  return new Promise((resolve)=>{
    setTimeout(() => {
      resolve('任务1')
    }, 500);
  })
}

function fun2(){
  return new Promise((resolve)=>{
    setTimeout(() => {
      resolve('任务2')
    }, 100);
  })
}

async function totalTask(){
  await fun1().then((response)=>{
    console.log(response)
  })
  await fun2().then((response)=>{
    console.log(response)
  })
}
totalTask()

 若没有async:

 function fun1(){
  return new Promise((resolve)=>{
    setTimeout(() => {
      resolve('任务1')
    }, 500);
  })
}

function fun2(){
  return new Promise((resolve)=>{
    setTimeout(() => {
      resolve('任务2')
    }, 100);
  })
}

function totalTask(){
fun1().then((response)=>{
    console.log(response)
  })
fun2().then((response)=>{
    console.log(response)
  })
}
totalTask()

 

五、给出await与async在实际应用中的例子:

1. async/await 的优势在于处理 then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作

/**
 * 传入参数 n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n + 200,这个值将用于下一步骤
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

现在用 Promise 方式来实现这三个步骤的处理

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// c:var	est>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

输出结果 result 是 step3() 的参数 700 + 200 = 900doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

如果用 async/await 来实现呢,会是这样:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

2. 还有更酷的

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}

这回先用 async/await 来写:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

// c:var	est>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms

除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

参考文章:https://segmentfault.com/a/1190000007535316

穷则独善其身,达则兼济天下……
原文地址:https://www.cnblogs.com/hmy-666/p/14528296.html