JS中异步详解promise对象和async/await对比

异步

本文大部分例子都能够直接在浏览器的控制台运行
在这里插入图片描述

ES6promise

不使用promise的异步回调

function runAsync(callback){
    setTimeout(function(){
        console.log('执行完成');
        callback('随便什么数据');
    }, 2000);
}

runAsync(function(data){
    console.log(data);
});

在这里插入图片描述

使用promise的异步函数(以链式的方式书写异步回调)

function runAsync() {
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('随便什么数据');
        }, 2000);
    });
    return p;            
}
runAsync().then((data) => {console.log(data);} );

// 结果:两秒后输出 执行完成  随便什么数据

为什么要使用promise对象

如果只是使用callback回调函数,在面对多层回调时,不建议再写一个callback2在传入第一层callback中,那样就会陷入回调地狱

而使用promise对象的话,使用then方法可以继续写promise对象,然后再使用then进行链式的异步回调。

使用promise就是把回调函数放到了then方法中进行处理

functionA(return promise1).then(return promise2).then( console.log(data) )

例如:三层回调

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务1执行完成');
            resolve('随便什么数据1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务2执行完成');
            resolve('随便什么数据2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务3执行完成');
            resolve('随便什么数据3');
        }, 2000);
    });
    return p;            
}
runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

//promise对象的状态
Promise {<pending>}__proto__: Promise[[PromiseStatus]]: "fulfilled"[[PromiseValue]]: undefined
// 结果
VM912:5 异步任务1执行完成
VM912:33 随便什么数据1
VM912:15 异步任务2执行完成
VM912:37 随便什么数据2
VM912:25 异步任务3执行完成
VM912:41 随便什么数据3

reject的用法

resolve是执行成功的情况,而执行失败的情况下会将promise对象的状态置为rejected,然后就可以在then方法中捕捉到在执行失败情况下的回调

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的随机数
            if(num<=5){
                resolve(num);
            }
            else{
                reject('数字太大了');
            }
        }, 2000);
    });
    return p;            
}

getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);

Promise {<pending>}__proto__: Promise[[PromiseStatus]]: "fulfilled"[[PromiseValue]]: undefined
// 结果
VM1475:24 rejected
VM1475:25 数字太大了

两层回调(运气好两种情况都有了)

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的随机数
            if(num<=5){
                resolve(num);
            }
            else{
                reject('数字太大了');
            }
        }, 2000);
    });
    return p;            
}

getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
        return getNumber();
    }
).then( function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    } );
Promise {<pending>}
VM1761:24 rejected
VM1761:25 数字太大了
VM1761:29 resolved
VM1761:30 4

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:

img 或者 img

catch的用法

作用:和上面实例程序的then中的第二个参数一样,用来指定reject的回调

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

效果和写在then的第二个参数里面一样。

但是它还有另外的一个作用如果在执行resolve的回调(then方法中的第一个参数)时,如果代码出错了(抛出异常),那么并不会卡死JS,而是会进入到catch方法中。代码如下

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不再往下运行了。但是在这里,会得到这样的结果:

img

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

all的用法

Promise的all方法提供了并行执行异步操作的能力,并且在所有的异步操作执行完成后才执行回调时长以最久的异步为准

var resolveAfter2Seconds = function() {
  console.log("starting slow promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
};

var resolveAfter1Second = function() {
  console.log("starting fast promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
};
var concurrentPromise = function() {
  console.log('==CONCURRENT START with Promise.all==');
  return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then((messages) => {
    console.log(messages[0]); // slow
    console.log(messages[1]); // fast
  });
}
setTimeout(concurrentPromise, 2000); 


VM171:21 ==CONCURRENT START with Promise.all==
VM171:2 starting slow promise
VM171:12 starting fast promise
VM171:16 fast promise is done
VM171:6 slow promise is done
VM171:23 slow
VM171:24 fast

使用promise.all来执行,all接受一个数组参数

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then( (results) => console.log(results) )

all数组参数中的所有异步操作并行执行,等他们都执行完成后才会进入到then方法中,这三个异步操作的返回值放在results数组(resolve回调返回的值)中。

用途:打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

race的用法

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务1执行完成');
            resolve('随便什么数据1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务2执行完成');
            resolve('随便什么数据2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务3执行完成');
            resolve('随便什么数据3');
        }, 2000);
    });
    return p;            
}
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});
Promise {<pending>}
VM2198:5 异步任务1执行完成
VM2198:34 随便什么数据1
VM2198:15 异步任务2执行完成
VM2198:25 异步任务3执行完成
function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作 可以是网络请求
        setTimeout(function(){
            console.log('异步任务1执行完成');
            resolve('随便什么数据1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务2执行完成');
            resolve('随便什么数据2');
        }, 7000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务3执行完成');
            resolve('随便什么数据3');
        }, 5000);
    });
    return p;            
}
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});
Promise {<pending>}
VM2343:5 异步任务1执行完成
VM2343:34 随便什么数据1
VM2343:25 异步任务3执行完成
VM2343:15 异步任务2执行完成

race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作

async和await

async的作用

问题的关键在于,async函数是怎么处理他的返回值的,

使用return返回返回值
async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);
VM386:6 

Promise {: “hello async”}

    1. proto: Promise
    2. [[PromiseStatus]]: “fulfilled”
    3. [[PromiseValue]]: “hello async”

可以看到async函数的返回值是一个promise对象

如果在函数中直接return一个直接量,async会直接把这个直接量通过Promise.resolve()封装成Promise对象

Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。

如果async没有返回值

会返回一个undefined的promise对象

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

const result1 = testAsync();
console.log(result1);

VM408:6 Promise {<fulfilled>: undefined}
__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: undefined

联想promise的特点:无等待,

所以在没有await的情况下执行async函数,他会立刻执行,返回一个promise对象,并且不会阻塞后边的语句。这种情况下async就和普通的返回promise对象的函数没有啥区别了,仅仅是简写了一下而已。

所以为什么要使用asyn/await,关键点就在于await关键字

await在等待啥

await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。

例如:

function getSomething() {
    return "something";
}

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

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    const v3 = testAsync();
    console.log(v1);
    console.log(v2);
    console.log(v3);
}

test();
// 结果
VM553:13 something
VM553:14 hello async
VM553:15 Promise {<fulfilled>: "hello async"}

await会直接把async函数的promise对象中的值拿出来

await等到了要等的东西之后

如果await等到了一个非promise对象的其他值

那么await表达式的运算结果就是它等待的东西

如果等到了一个promise对象

await会阻塞后边的代码,等待着promise对象的回调完成,也就是得等到resolve的值,作为await表达式的最终结果。也是就是上边示例代码中所写的await会直接把promise对象中的值拿出来

async/await的实际的作用

上边说了如果只有async函数,仅仅是简写了生成promise对象的过程,现在在知道了await的作用后,它们在一起的实际应用是什么,为什么能替代promise /then?

举例用setTimeout模拟耗时操作(一般情况下耗时操作就是网络请求)

promise对象的写法:

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});

 // 结果
 got long_time_value

async/await的写法

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();

// 结果
Promise {<pending>}
VM1574:9 long_time_value

目前看来async/await对于异步对象的处理差别并不明显,甚至使用async/await还需要多写代码

但是它们的优势在于处理多层回调的链式处理上,被称作是异步处理的终极方案。

单一的 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\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

使用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();

最终的效果是一样的,但是使用async/await方式的代码更加优雅。

time2的值是 await表达式最后的结果,此时await关键字等待是一个promise对象,会阻塞后边的代码,直到promise对象的resolve回调执行完成(即300+200的操作,和等待的n的时长)。

time3则会等待time2的最后结果。

并行运行async/await(官网例子)

var resolveAfter2Seconds = function() {
  console.log("starting slow promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
};

var resolveAfter1Second = function() {
  console.log("starting fast promise");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
};

var concurrentPromise = function() {
  console.log('==CONCURRENT START with Promise.all==');
  return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then((messages) => {
    console.log(messages[0]); // slow
    console.log(messages[1]); // fast
  });
}

var parallel = async function() {
  console.log('==PARALLEL with await Promise.all==');
  
  // Start 2 "jobs" in parallel and wait for both of them to complete
  await Promise.all([
      (async()=>console.log(await resolveAfter2Seconds()))(),
      (async()=>console.log(await resolveAfter1Second()))()
  ]);
}

// wait again
setTimeout(concurrentPromise, 2000); // same as concurrentStart

// wait again
setTimeout(parallel, 4000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"

运行结果

在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值