ES6的异步操作之promise用法和async函数的具体使用(2)

const getAjax = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState === 4 && this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onreadystatechange = handler; xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); xhr.send(); }); return promise; }; getAjax("/test.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('出错了', error); });

async / await 基本用法

当 async 函数执行的时候,一旦遇到 await 就会先等到 await 后的异步操作完成,再接着执行函数体内之后的语句。

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。

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

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

async function f() { throw new Error('出错了'); } f().catch( e => console.log(e)) // Error: 出错了

await 命令

正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() { return await 123; // 等同于 return 123; } f().then(v => console.log(v)) // 123

await 命令后面的 Promise 对象如果变为 rejected 状态,则错误会被 catch 方法的回调函数接收到。

任何一个 await 语句后面的 Promise 对象变为 rejected 状态,那么整个 async 函数就会中断执行。

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作,有两个解决办法:

第一种方法是可以将 await 放在 try...catch 结构里面,这样不管这个异步操作是否成功,后面的代码都会执行。

async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello dora'); } f().then(v => console.log(v)) // hello dora

另一种方法是 await 后面的 Promise 对象再跟一个 catch 方法,处理前面可能出现的错误。

async function f() { await Promise.reject('出错了').catch(e => console.log(e)); return await Promise.resolve('hello dora'); } f().then(v => console.log(v)) // 出错了 // hello dora

使用注意点

1. 错误处理

前面已经说过,await 命令后面的 Promise 对象,运行结果可能是 rejected,所以防止出错的方法,就是最好把 await 命令放在 try...catch 代码块中。如果有多个 await 命令,可以统一放在 try...catch 结构中,如果只有一个 await,可以使用上例中的 catch 捕获 await 后面的 promise 抛出的错误。

const superagent = require('superagent'); const NUM_RETRIES = 3; async function test() { let i; for (i = 0; i < NUM_RETRIES; i++) { try { await superagent.get('/api/xxx'); break; } catch(err) {} } } test();

上面代码中,使用 try...catch 结构,实现多次重复尝试。如果 await 操作成功,就会使用 break 语句退出循环;如果失败,会被 catch 语句捕捉,然后进入下一轮循环。

2. 多个 await 异步操作并发执行

多个 await 命令后面的异步操作,如果不存在继发关系(即互不依赖),最好让它们同时触发,以缩短程序的执行时间。

// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;

3. forEach 等数组遍历方法的参数为 async 函数时是并发执行的

只有 async 函数内部是继发执行,外部不受影响,因此 forEach()、map() 等数组遍历方法的参数改成 async 时是并发执行的。

function dbFuc() { //这里不需要 async let docs = [{}, {}, {}]; // 会得到错误结果 docs.forEach(async (doc)=> { await funPromise(doc); }); }

上面代码会得到错误结果,原因是这时三个 funPromise(doc) 操作是并发执行的,也就是同时执行,而不是继发执行。因此正确的写法是采用 for 循环。

async function dbFuc() { let docs = [{}, {}, {}]; for (let doc of docs) { await funPromise(doc); } }

如果需要并发执行,可使用 Promise.all() 方法。

async function dbFuc() { let docs = [{}, {}, {}]; let promises = docs.map((doc) => funPromise(doc)); let results = await Promise.all(promises); return results; }

有一组异步操作,需要按照顺序完成。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/2afb896323adea13899c2543d39ac155.html