目前异步编程最好的解决方案: async、await
处理命令时,不必等待结果返回即可执行其他命令。当命令返回时再执行响应操作。
| |
getData -------------> doing
| |
do a() |
do b() |
| |
cb() <------------- done
| |
如图,程序执行getData方法获取数据,数据没有马上返回,继续执行a和b,数据返回后再执行getData的回调方法。getData方法称为异步方法。
回调函数在工作中经常用到,例子如下:
function getData(url, cb) {
// 请求url的数据 doing
// ...
// 请求url的数据 done
// 请求结束后执行cb方法 cb称为回调函数
cb()
}
// 使用
getData('xxxxxxx.json', function() {
// doing something
});
回调函数虽然用着简单却极易产生回调地狱,例子如下:
ajax('xxxxxxx.json', function() {
// doing something 1
ajax('xxxxxxx.json', function() {
// doing something 2
ajax('xxxxxxx.json', function() {
// doing something 3
ajax('xxxxxxx.json', function() {
// doing something 4
});
});
});
});
按钮上绑定click事件后,按钮被点击则触发响应的事件。这就事件监听。
一般事件监听会是一个单独的类,使用的时候去继承这个类就可以了。
例子如下:
// getData继承事件机制
class getData extends Event {
getDataFn() {
// doing something
// getData触发get事件
this.trigger('get');
}
}
let getDataInstance = new getData();
// getData绑定get事件,事件被触发则执行cb方法
getDataInstance.on('get', cb);
// 开始获取数据,获取成功后会触发get事件,然后执行cb方法
getDataInstance.getDataFn();
但实际项目中,某些场景下发布-订阅更常用,其实跟事件绑定是类似的,区别是将时间的绑定、触发单独放在一个主体上。
例子如下:
// 主体
let subject = new Subjet();
// 像向体订阅getData通知
subject.subscribe('getData', function(data) {
// do something...
});
// 获取数据方法
function getDate(params) {
// do something...
// 获取数据后主题发布getData通知
subject.publish('getData');
}
用Promise可以包装出异步任务,她会返回一个Promise对象,Promise的.then
来解决异步任务的回调,并且支持链试调用。
Promise详细文档请阅读:阮一峰老师的ES6入门:Promise
例子如下:
var getData = new Promise((resolve, reject) => {
// do something... 比如请求http获取数据
// 成功了
resolve('成功了');
});
getData
.then(
sucRes => {
// 成功回调
// Promise对象支持链式调用,在`.then`方法从return出的参数会被包装成Promise,供下一个。then方法使用
// 详细的api说明百度一下有很多
return '链式调用';
},
err => {
// 失败回调
}
)
.then(
sucRes => {
// 成功回调
},
err => {
// 失败回调
}
);
Promise.all() 也是经常使用的方法,用于将多个 Promise 实例,包装成一个新的 Promise 实例。
我们直接看例子:
// 现在假设有P1,P2,P3 三个Promise实例,我们需要这三个实例都完成后,再执行一些操作。
// 这时就可以用Promise.all()
Promise.all([P1, P2, P3]).then(
sucRes => {
// P1,P2,P3都成功时,执行回调
},
err => {
// P1,P2,P3有任意一个Promise失败时,执行回调
}
);
async
是 Generator
函数的语法糖,不知道Generator
的可以查看任一峰老师的ES6:Generator。
async
返回的值,会被包装成Promise对象
例子如下:
// async 返回的值,会被包装成Promise对象,可以提供链式调用
async function getData(params) {
return '完成';
}
getData.then(suc => {});
await
可以让异步代码看起来像同步代码,就像他的文字含义一样。await:等待
例子如下:
// 异步任务P1 3秒后完成
function P1(params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('完成');
}, 3000);
});
}
// async 内部试用await控制异步任务
async function getData(params) {
// getData方法执行后,会等待异步任务P1执行完成(3秒后),再返回成功了
// 这里的异步处理,跟同步的代码是很像的
// .catch 处理异常,async方法返回的是一个Promise对象
// 当遇到没有处理的Error时候,会执行reject直接返回,跳过接下来的任务
await P1().catch(err => {});
// P1完成后,do something other
return '完成了';
}
上面提到的async
可以简单理解为Generator + 执行器
。详情:阮一峰老师的ES6入门:Generator
Generator函数在我理解,其实是异步编程方法的一个中间产物,能用但是不够好用。
Generator可以控制函数按步骤执行,例子如下:
// `*`可以声明一个Generator函数,yield定义一个停顿点(返回值)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator函数的执行,需要手动控制,编程的时候代码相当繁琐。因此需要搭配一个执行器,比如Thunk函数。
如果你想深入了解 Generator、执行器、Async函数之间的关系,推荐以下文章:
Generator 函数的含义与用法
执行器:Thunk 函数的含义和用法
co 函数库的含义和用法
async 函数的含义和用法