Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

好用的 async/await #15

Open
libin1991 opened this issue Jan 28, 2018 · 0 comments
Open

好用的 async/await #15

libin1991 opened this issue Jan 28, 2018 · 0 comments

Comments

@libin1991
Copy link
Owner

用 Async 函数简化异步代码

ES7 提出的async 函数,终于让 JavaScript 对于异步操作有了终极解决方案。No more callback hell。
async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。

Async - 定义异步函数(async function someName(){...})

  • 自动把函数转换为 Promise
  • 当调用异步函数时,函数返回值会被 resolve 处理
  • 异步函数内部可以使用 await

Await - 暂停异步函数的执行 (var result = await someAsyncCall();)

  • 当使用在 Promise 前面时,await 等待 Promise 完成,并返回 Promise 的结果
  • await只能和 Promise 一起使用,不能和 callback 一起使用
  • await 只能用在 async 函数中
  • 想较于 Generator,Async 函数的改进在于下面四点:
  • 内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
  • 更好的语义。asyncawait 相较于 *yield 更加语义化
  • 更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)
  • 返回值是 Promise。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用

Async 与其他异步操作的对比

先定义一个 Fetch 方法用于获取 github user 的信息:

function fetchUser() { 
    return new Promise((resolve, reject) => {
        fetch('https://api.github.com/users/superman66')
        .then((data) => {
            resolve(data.json());
        }, (error) => {
            reject(error);
        })
    });
}

Promise 方式

/**
 * Promise 方式
 */
function getUserByPromise() {
    fetchUser()
        .then((data) => {
            console.log(data);
        }, (error) => {
            console.log(error);
        })
}
getUserByPromise();

Promise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise的 then() 方法,如果处理流程复杂的话,整段代码将充满 then。语义化不明显,代码流程不能很好的表示执行流程。
Generator 方式

/**
 * Generator 方式
 */
function* fetchUserByGenerator() {
    const user = yield fetchUser();
    return user;
}

const g = fetchUserByGenerator();
const result = g.next().value;
result.then((v) => {
    console.log(v);
}, (error) => {
    console.log(error);
})

Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行。
async 方式

/**
 * async 方式
 */
 async function getUserByAsync(){
     let user = await fetchUser();
     return user;
 }
getUserByAsync()
.then(v => console.log(v));

async 函数完美的解决了上面两种方式的问题。流程清晰,直观、语义明显。操作异步流程就如同操作同步流程。同时 async 函数自带执行器,执行的时候无需手动加载。

多个并发:

  • Async/Await 底层依然使用了 Promise。

  • 多个异步函数同时执行时,需要借助 Promise.all

    async function getABC() {
    let A = await getValueA(); // getValueA 花费 2 秒
    let B = await getValueB(); // getValueA 花费 4 秒
    let C = await getValueC(); // getValueA 花费 3 秒

    return A*B*C;
    

    }

每次遇到 await 关键字时,Promise 都会停下在,一直到运行结束,所以总共花费是 2+4+3 = 9 秒。await把异步变成了同步。

async function getABC() {
  // Promise.all() 允许同时执行所有的异步函数
  let results = await Promise.all([ getValueA, getValueB, getValueC ]); 

  return results.reduce((total,value) => total * value);
} 

语法

async 函数返回一个 Promise 对象

async 函数内部 return 返回的值。会成为 then 方法回调函数的参数。

async function  f() {
    return 'hello world'
};
f().then( (v) => console.log(v)) // hello world

如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。

async function e(){
    throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));

async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变

也就是说,只有当 async 函数内部的异步操作都执行完,才会执行 then 方法的回调。

const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
    await delay(1000);
    await delay(2000);
    await delay(3000);
    return 'done';
}

f().then(v => console.log(v)); // 等待6s后才输出 'done'

正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise
如下面这个例子:

async function  f() {
    return await 1
};
f().then( (v) => console.log(v)) // 1

如果返回的是 reject 的状态,则会被 catch 方法捕获。

Async 函数的错误处理

async 函数的语法不难,难在错误处理上。
先来看下面的例子:

let a;
async function f() {
    await Promise.reject('error');
    a = await 1; // 这段 await 并没有执行
}
f().then(v => console.log(a));

如上面所示,当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。
解决办法:可以添加 try/catch

// 正确的写法
let a;
async function correct() {
    try {
        await Promise.reject('error')
    } catch (error) {
        console.log(error);
    }
    a = await 1;
    return a;
}

correct().then(v => console.log(a)); // 1

如果有多个 await 则可以将其都放在 try/catch 中。

如何在项目中使用

依然是通过 babel 来使用。
只需要设置 presetsstage-3 即可。
安装依赖:

npm install babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime

修改.babelrc:

"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]

这样就可以在项目中使用 async 函数了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant