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

ES6之async函数 #45

Open
2018212632 opened this issue Nov 14, 2020 · 0 comments
Open

ES6之async函数 #45

2018212632 opened this issue Nov 14, 2020 · 0 comments
Labels

Comments

@2018212632
Copy link
Owner

ES6之async函数

async是什么

async是Generator的语法糖。与Generator比较,在语法上,async仅仅是把 * 换成 async;yield 换成了 await 而已。

模拟一个异步例子:

// Generator写法
function f(time) {
    return new Promise(
        (resolve, reject) => {
            setTimeout(() => {
                console.log("after time:", time)
                resolve()
            }, time)

        }
    )
}


function* gen() {
    yield f(1000)
    yield f(2000)
}

var g = gen()

如果要获取结果,需要手动执行Genrator函数:

g.next().value.then(()=>{console.log('successs')})
g.next().value.then(()=>{console.log('successs')})

用 async 改写:

async function asyncGen() {
    await f(1000).then(()=>{console.log('success')})
    await f(2000).then(()=>{console.log('success')})
}

如果要获取结果,直接执行asyncGen函数:

asyncGen()

可以发现 async 函数与 Geneartor 函数的区别:

  1. async 内置执行器,不需要像Geneartor那样引入co模块再能自动执行
  2. 更好的语义,async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  3. co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  4. async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

async与Promise

async与Promise都是ES6对异步操作的方案,那async对于一些场景有什么优劣势?

代码更简洁

/**
 * 处理单个异步操作
 */
function fetch() {
  return (
    fetchData()
    .then(() => {
      return "done"
    });
  )
}

async function fetch() {
  await fetchData()
  return "done"
};
/**
 * 对单个异步操作返回值进行操作
 */
function fetch() {
  return fetchData()
  .then(data => {
    if (data.moreData) {
        return fetchAnotherData(data)
        .then(moreData => {
          return moreData
        })
    } else {
      return data
    }
  });
}

async function fetch() {
  const data = await fetchData()
  if (data.moreData) {
    const moreData = await fetchAnotherData(data);
    return moreData
  } else {
    return data
  }
};
/**
 * 对于流操作,一系列异步操作有着关联,一个异步操作需要使用到上一个异步操作的结果
 */
function fetch() {
  return (
    fetchData()
    .then(value1 => {
      return fetchMoreData(value1)
    })
    .then(value2 => {
      return fetchMoreData2(value2)
    })
  )
}

async function fetch() {
  const value1 = await fetchData()
  const value2 = await fetchMoreData(value1)
  return fetchMoreData2(value2)
};

错误处理

function fetch() {
  try {
    fetchData()
      .then(result => {
        const data = JSON.parse(result)
      })
      .catch((err) => {
        console.log(err)
      })
  } catch (err) {
    console.log(err)
  }
}

这段代码中,Promise异步操作可以使用try cathch捕获fetchData()一些promise构造的错误,不能捕获JSON.parse中的错误,如果要捕获这个错误,需要添加catch函数重复一些异常处理的逻辑。

在实际项目中,错误处理逻辑可能会很复杂,这会导致冗余的代码。

async function fetch() {
    try {
        const data = JSON.parse(await fetchData())
    } catch(err) {
        console.log(err)
    }
}

async/await 的出现使得 try/catch 就可以捕获同步和异步的错误。

async 地狱

async 地狱指开发者贪图语法上的简洁,让原本可以并行执行的操作,变成顺序执行,从而影响了性能。

例子一:

(async () => {
  const getList = await getList();
  const getAnotherList = await getAnotherList();
})();

getList 和 getAnotherList 逻辑上没有依赖关系,但是这种写法,虽然简洁,却导致getAnotherList() 必须在 getList() 执行完之后执行,浪费了一倍的时间。

改进:

(async () => {
    const listPromise = getList()
    const anotherListPromise = getAnotherList()
    await listPromise
    await anotherListPromise
})()

也可以使用Promise.all()

(async () => {
    Promise.all([getList(), getAnoterList()]).then()
})()

例子二:
如果函数中既有依赖关系,又有并发的关系,该怎么处理呢?

(async () => {
  const listPromise = await getList();
  const anotherListPromise = await getAnotherList();

  // do something

  await submit(listData);
  await submit(anotherListData);

})();

基本步骤:

  1. 找出依赖关系,例子中,submit(listData)需要在getList()之后,submit(anotherListData)在getAnotherList()之后
  2. 将互相依赖的语句包裹在 async 函数中
async function handleList() {
  const listPromise = await getList();
  // ...
  await submit(listData);
}

async function handleAnotherList() {
  const anotherListPromise = await getAnotherList()
  // ...
  await submit(anotherListData)
}
  1. 并发执行 async 函数
async function handleList() {
  const listPromise = await getList();
  // ...
  await submit(listData);
}

async function handleAnotherList() {
  const anotherListPromise = await getAnotherList()
  // ...
  await submit(anotherListData)
}

// 方法一
(async () => {
  const handleListPromise = handleList()
  const handleAnotherListPromise = handleAnotherList()
  await handleListPromise
  await handleAnotherListPromise
})()

// 方法二
(async () => {
  Promise.all([handleList(), handleAnotherList()]).then()
})()

继发与并发

问题:给定一个url数组,如何实现接口的继发和并发?

async继发实现:

// 继发一
async function loadData() {
    const res1 = await fetch(url1)
    const res2 = await fetch(url2)
    const res3 = await fetch(url3)
    return 'done'
}

// 继发二
async function loadData(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}

async并发实现:

// 并发一
async function loadData() {
  var res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)]);
  return "whew all done";
}
// 并发二
async function loadData(urls) {
  // 并发读取 url
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

async错误捕获

尽管我们可以使用 try catch 捕获错误,但是当我们需要捕获多个错误并做不同的处理时,很快 try catch 就会导致代码杂乱,就比如:

async function asyncTask(cb) {
    try {
       const user = await UserModel.findById(1);
       if(!user) return cb('No user found');
    } catch(e) {
        return cb('Unexpected error occurred');
    }

    try {
       const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    } catch(e) {
        return cb('Error occurred while saving task');
    }

    if(user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, 'Task Created');
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    if(savedTask.assignedUser.id !== user.id) {
        try {
            await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    cb(null, savedTask);
}

为了简化这种错误的捕获,我们可以给 await 后的 promise 对象添加 catch 函数,为此我们需要写一个 helper:

// to.js
export default function to(promise) {
   return promise.then(data => {
      return [null, data];
   })
   .catch(err => [err]);
}

整个错误捕获的代码可以简化为:

import to from './to.js';

async function asyncTask() {
     let err, user, savedTask;

     [err, user] = await to(UserModel.findById(1));
     if(!user) throw new CustomerError('No user found');

     [err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
     if(err) throw new CustomError('Error occurred while saving task');

    if(user.notificationsEnabled) {
       const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
       if (err) console.error('Just log the error and continue flow');
    }
}

async的讨论

async 会取代 Generator 吗?

Generator 本来是用作生成器,使用 Generator 处理异步请求只是一个比较 hack 的用法,在异步方面,async 可以取代 Generator,但是 async 和 Generator 两个语法本身是用来解决不同的问题的。

async 会取代 Promise 吗?

  1. async 函数返回一个 Promise 对象

  2. 面对复杂的异步流程,Promise 提供的 all 和 race 会更加好用

  3. Promise 本身是一个对象,所以可以在代码中任意传递

  4. async 的支持率还很低,即使有 Babel,编译后也要增加 1000 行左右。

@2018212632 2018212632 added the ES6 label Nov 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant