异步编程之setTimeout+Promise+Generator+AsyncAwait

setTimeout/Promise/AsyncAwait的区别

(1)setTimeout

setTimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行

(2)Promise

Promise本身是同步的立即执行函数,当在exector中执行resolve或者reject的时候,此时是异步操作,会先执行then/catch等,等主栈完成后,才会去调用resolve/reject中存放的方法执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
console.log('script start');
let promise1 = new Promise(function(resolve) {
    console.log('promise1');
    resolve();
    console.log('promise1 end');
}).then(function() {
    console.log('promise2');
})
setTimeout(function() {
    console.log('settimeout');
})
console.log('script end');
//输出顺序:script start -> promise1 -> promise1 end -> script end -> promise2 
//-> settimeout
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
console.log(1)
setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(8)
        resolve()
    }).then(() => {
        console.log(6)
    })
}, 100)
console.log(3)
setTimeout(() => {
    console.log(4)
    Promise.resolve().then(() => {
        console.log(7)
    })
}, 0)
Promise.resolve().then(() => {
    console.log(9)
})
console.log(5)
//输出顺序:1-> 3 -> 5 -> 9 -> 4 -> 7 ->2 -> 8 -> 6

(3)async/await

async函数返回一个Promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了async函数体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
async1();
console.log('script end');
//输出顺序:script start -> async1 start -> async2 -> script end -> async1 end

异步编程的发展历程

一个任务分成两段,先执行一段,转而执行其他任务,等做好了准备转而执行第二段

回调函数->Promise->Generator->async await

(1)回调函数(callback)

函数B作为参数(函数引用)传递到另一个函数A中,并且这个函数A执行函数B。我们就说函数B叫做回调函数

✔️优点:解决了异步问题

❌缺点:回调地狱,多个回调函数嵌套的情况,使代码看起来十分混乱,不易于维护

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//定义主函数
function A(callback) {
    callback();
    console.log('我是主函数')
}

//定义回调函数
function B() {
    setTimeout("console.log('我是回调函数')", 3000); //模仿耗时操作
}

//调用主函数,将函数B传进去
A(B);
//输出顺序:我是主函数->我是回调函数

(2)Promise

用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作

✔️优点:可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

❌缺点:

  • 无法取消Promise
  • 错误需要通过回调函数来捕获
  • 当处于pending状态时,无法得知目前进展到哪一个阶段
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
new Promise(
    /*executor*/
    function(resolve, reject) {
        if ( /**success */ ) {
            //执行代码
            resolve();
        } else { /**fail */
            //...执行代码
            reject();
        }
    }
)
API 用法
Promise.then 实例化后的Promise对象可以进行链式调用
Promise.catch 捕获异常操作时出现的异常
Promise.all() 将数组中所有的任务执行完成之后,才能执行.then中的任务。全部为fulfilled时,它才会变成fulfilled,否则变成rejected
Promise.race() 一旦参数内有一个值的状态发生的改变,那么该Promise的状态就是改变的状态
Promise.resolve() 接受一个参数值,可以是普通的值,具有then()方法的对象和Promist实例
Promise.reject() 接受一个参数值reason,即发生异常的原因

(3)Generator

  • 在function关键字后加一个*,那么这个函数就称之为generator函数
  • 函数体有关键字yield,后面跟每一个任务,也可以有return关键字,保留一个数据
  • 通过next函数调用,几个调用,就是几个人任务执行

yield后面的星号也跟生成器有关

✔️优点:没有了Promise的一堆then(),异步操作更像同步操作,代码更加清晰

❌缺点:不能自动执行异步操作,需要写多个next()方法,需要配合使用Thunk函数和co模块才能做到自动执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
//next方法可以带一个参数,该参数就会被当成上一个yield表达式的返回值
t.next(1); //第一次调用next函数时,该参数就会被当作上一个yield表达式的返回值
t.next(2); //a输出2
t.next(3); //b输出3
t.next(4); //c输出4
t.next(5); //d输出5
//输出顺序:2->3->4->5

(4)async await

(与generator有关),async await是语法糖,内部是generator+promise实现async函数就是将Generator函数的星号(*)替换成async,将yield替换成await

(与promise有关)

放在一个函数前的async有两个作用:

  • 使函数总是返回一个promise
  • 允许在这其中使用await

promise前面的await关键字能够使JavaScript等待,直到promise处理结束。然后:

  • 如果它是一个错误,异常就产生了,就像在那个地方调用了throw error一样
  • 否则,它会返回一个结果,我们可以将它分配给一个值

✔️优点:代码清晰,不用像Promise写一大堆then链,处理了回调地狱的问题。很显然,async/await比promise更容易理解

❌缺点:await将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用await会导致性能上的降低

1
2
3
4
5
6
7
async function Data() {
    return 'ABing';
}
async function getData() {
    console.log(await Data());
}
getData(); //输出结果:ABing

(5)为什么Async/Await更好?

  • 简洁:由示例而知,使用Async/Await明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显

  • 错误处理:Async/Await让try/catch可以**同时处理同步和异步错误。**在下面的promise示例中,try/catch不能处理JSON.parse的错误,因为它在Promise中。我们需要使用.catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂

  • 可读性:使用async/await编写可以大大提高可读性

  • 错误栈:Promise链中返回的错误栈没有给出错误发生位置的线索。更糟糕的是,它会误导我们:错误站中唯一的函数名为callAPromise,然而它和错误没有关系(文件名和行号还是有用的)

  • 调试:最后一点,也是非常重要的一点在于,async/await能够使得代码调试更简单

Built with Hugo
主题 StackJimmy 设计