[ES8] async
#
async函数就是Generator函数的语法糖。
async函数完全可以看作多个异步操作,
包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
async函数对 Generator 函数的改进:
(1)内置执行器。
Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。
async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,
可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
(4)返回值是Promise。
async函数的返回值是Promise对象,
这比Generator函数的返回值是Iterator对象方便多了。
可以用then方法指定下一步的操作。
Async函数有多种使用形式。
// 函数声明 async function foo() {} // 函数表达式 const foo = async function () {}; // 对象的方法 let obj = { async foo() {} }; // 箭头函数 const foo = async () => {};
#
var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; // generator var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; //写成async函数 var asyncReadFile = async function (){ var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; var result = asyncReadFile();//执行
(1)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 f() { throw new Error('出错了'); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出错了
(2)async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
#
async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log) // "ECMAScript 2017 Language Specification"
(3)正常情况下,await命令后面是一个Promise对象。
#如果不是,会被转成一个立即resolve的Promise对象。
async function f() { return await 123; } f().then(v => console.log(v)) // 123
#await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
async function f() { await Promise.reject('出错了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了
#只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。
async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 }
#为了避免这个问题,可以将第一个await放在try...catch结构里面,这样第二个await就会执行。
async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world
#另一种方法是await后面的Promise对象再跟一个catch方面,处理前面可能出现的错误。
async function f() { await Promise.reject('出错了') .catch(e => console.log(e)); return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // 出错了 // hello world
#async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args){ // ... } // 等同于 function fn(args){ return spawn(function*() { // ... }); } function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
#指定多少毫秒后输出一个值。
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value) } asyncPrint('hello world', 50);
#多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
#多个请求并发执行
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
#await命令只能用在async函数之中,如果用在普通函数,就会报错。
//错 async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); } //错 async function dbFuc(db) { let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); } //对 async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
与Promise、Generator的比较
假定某个DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。
如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。
#Promise的写法。
function chainAnimationsPromise(elem, animations) { // 变量ret用来保存上一个动画的返回值 var ret = null; // 新建一个空的Promise var p = Promise.resolve(); // 使用then方法,添加所有动画 for(var anim in animations) { p = p.then(function(val) { ret = val; return anim(elem); }); } // 返回一个部署了错误捕捉机制的Promise return p.catch(function(e) { /* 忽略错误,继续执行 */ }).then(function() { return ret; }); }
#Generator函数的写法。
spawn函数是自动执行器
function chainAnimationsGenerator(elem, animations) { return spawn(function*() { var ret = null; try { for(var anim of animations) { ret = yield anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }); }
#Async函数的写法。
async function chainAnimationsAsync(elem, animations) { var ret = null; try { for(var anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }
#
import path from 'path'; import fs from 'fs'; let a = path.join(__dirname, 'a'); let b = path.join(__dirname, 'b'); let c = path.join(__dirname, 'c'); let d = path.join(__dirname, 'd'); let e = path.join(__dirname, 'e'); //console.log(p) //let aa = mkdir(a); //let bb = mkdir(b); //let cc = mkdir(c); //let dd = mkdir(d); //let ee = mkdir(e); let content = [a, b, c, d, e]; let sequence = []; async function xx() { console.log('in'); //sequence = content.map((v) => mkdir(v)); //for (let v of content) { // let k = mkdir(v); // sequence.push(k); //} //console.log(sequence); //await Promise.all(sequence);//异步 for (let v of content) { await mkdir(v);//有序 } } xx(); //for (let v of content) { // mkdir(v);//异步 //} function mkdir(fp) { return new Promise((resolve, reject)=> { fs.mkdir(fp, (error)=> { if (error) { reject(error); } else { console.log(fp) resolve(); } }) }); }