zhaobinglong / myBlog

https://zhaobinglong.github.io/myBlog/
MIT License
7 stars 0 forks source link

异步解决方案之async/await #47

Open zhaobinglong opened 4 years ago

zhaobinglong commented 4 years ago

背景

Javascript引擎从设计伊始就是单线程的,然而真实世界的事件往往是多头并进的。为了解决这个问题,浏览器通过UI事件、网络事件及回调函数引入了一定程度的并发执行可能(但Javascript引擎仍然是单线程的,这种并发,类似于其它语言的协程)。

在复杂的程序里,回调函数是一个坑,它使得代码变得支离破碎,难以阅读。后来出现了Promise,但也没能完全解决上述问题。

Javascript的最新方法是async/await,一种在其它语言中早已实现的方案。

async和Promise

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

参考

边城大佬关于这两次关键词的解释 https://segmentfault.com/a/1190000007535316

zhaobinglong commented 4 years ago

await和promise的执行顺序

function a(){
    return new Promise(res => {
        console.log(3)
        setTimeout(() => {
            console.log(4)
            res()
        }, 2000)
    })
}

function b(){
    return new Promise(async res => {
        console.log(1)
        res()
        console.log(2)
        await a() //a()是一个promise,await会阻塞当前代码块中的代码执行,交出主线程,程序继续执行后面的同步代码
        console.log(5)
    })
}

b().then(() => {
    console.log(6)
})
zhaobinglong commented 4 years ago

实现定时输出

    const arr = [1,2,3,4,5,6]
    arr.forEach(async (item) => {
        await sleep(item)
        console.log('after', item)
    })
    function sleep(item){
        return new Promise(res => {
            setTimeout(() => {
                res(console.log('before', item))
            }, 2000)
        })
    }
zhaobinglong commented 4 years ago

async原理

async就是一个函数修饰符,它可以被放在任意一个函数前面,async 用于申明一个 function 是异步的,被它修饰的函数,返回对象会变成一个promise

// 如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
function testAsync() {
    return "hello async";
}
testAsync() // 返回字符串hello asyn
async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result); // 返回promise,then中是hello asyn

本质

async 函数是什么?一句话,它就是 Generator 函数的语法糖。async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

zhaobinglong commented 4 years ago

await

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数, 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。 优点在于处理 then 链,await会让我们的异步代码看起来就像是同步代码一样,一行一行

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

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

test();

本质:协程暂停

第一步,协程A开始执行。 第二步,协程A执行到一半,进入暂停,执行权转移到协程B。 第三步,(一段时间后)协程B交还执行权。 第四步,协程A恢复执行。

zhaobinglong commented 4 years ago

Generator函数

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。它不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。

整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。Generator 函数的执行方法如下。

定义一个Generator函数

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

执行

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
zhaobinglong commented 4 years ago

测试题目1:分析执行顺序

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
async function testAsync() {
    return "hello async";
}
// 因为async就是把函数封装成了一个promise,所以就可以用then的方式来获取函数的返回
testAsync().then(v => {
    console.log(v);    // 输出 hello async
});
// 因为promise是可以reject的,所以await一定要放在try catch中
async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}