最近做钉钉项目涉及很多异步操作,使用起来还有点迷糊。查阅资料后在这里记录一下。
一、callback回调函数
- A callback is a function that is passed as an argument to another function and is executed after its parent function has completed。
- 回调函数就是将函数B作为参数传入到函数A中,当函数A执行完成后,再调用函数B。这个过程就叫回调。
举一个别人举过的..别人举过的例子:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。
实例代码
定义函数:
function f1(callback){
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
function f2(){
//doSomething
}
调用过程:
f1(f2);
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。
二、事件监听
采用事件驱动模式。
任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
- 监听函数有:on,bind,listen,addEventListener,observe
还是以f1和f2为例。首先,为f1绑定一个事件(采用jquery写法)。
f1.on('done',f2);
上面代码意思是,当f1发生done事件,就执行f2。
然后对f1进行改写:
function f1(){
settimeout(function(){ //f1的任务代码
f1.trigger('done');
},1000);
}
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
三、发布/订阅
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
- 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
首先,f2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", f2);
然后,f1进行如下改写:
function f1(){
setTimeout(function () {
// f1的任务代码
jQuery.publish("done");
}, 1000);
}
jQuery.publish("done")的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
四、Promises对象
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
const promise = new Promise(function(resolve,reject) {
... if(/\*异步操作成功\*/){
resolve(value)
}else{
reject(error)
}
})
Promises是一个构造函数,这个构造函数里有两个参数,分别是:resolve(成功之后的回调函数)、reject(失败之后的回调函数)。
因为promise表示的是一个异步操作,每当我们new一个promise实例,就表示一个具体的异步操作,那么这个异步操作的结果就只能有两种状态:成功/失败,两者都需要回调函数resolve/reject返回。所以内部拿到操作的结果后,无法使用return把操作结果返回给调用者,这时候只能用回调函数的形式来把成功或失败的结果返回给调用者。
简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:
f1().then(f2);
f1要进行如下改写(这里使用的是jQuery的实现):
function f1(){
var dfd = $.Deferred();
setTimeout(function () {
// f1的任务代码
dfd.resolve();
}, 500);
return dfd.promise;
}
这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。
比如,指定多个回调函数:
f1().then(f2).then(f3);
再比如,指定发生错误时的回调函数:
f1().then(f2).fail(f3);
而且,它还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。这种方法的缺点就是编写和理解,都相对比较难。
五、async/await方法
- 1、async/await更加语义化,async 是“异步”的简写,async function 用于申明一个 function 是异步的; await,可以认为是async wait的简写, 用于等待一个异步方法执行完成;
- 2、async/await是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)
- 3、可以通过多层 async function 的同步写法代替传统的callback嵌套
async function语法
- 自动将常规函数转换成Promise,返回值也是一个Promise对象
- 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
- 异步函数内部可以使用await
async function name([param[, param[, ... param]]]) { statements }
name: 函数名称。
param: 要传递给函数的参数的名称
statements: 函数体语句。
返回值: 返回的Promise对象会以async function的返回值进行解析,或者以该函数抛出的异常进行回绝。
await语法
- await 放置在Promise调用之前,await 强制后面点代码等待,直到Promise对象resolve,得到resolve的值作为await表达式的运算结果
- await只能在async函数内部使用,用在普通函数里就会报错
[return_value] = await expression;
expression: 一个 Promise 对象或者任何要等待的值。
返回值:返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
错误处理
在async函数里,无论是Promise reject的数据还是逻辑报错,都会被默默吞掉,所以最好把await放入try{}catch{}中,catch能够捕捉到Promise对象rejected的数据或者抛出的异常
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {reject('error')}, ms); //reject模拟出错,返回error
});
}
async function asyncPrint(ms) {
try {
console.log('start');
await timeout(ms); //这里返回了错误
console.log('end'); //所以这句代码不会被执行了
} catch(err) {
console.log(err); //这里捕捉到错误error
}
}
asyncPrint(1000);
如果不用try/catch的话,也可以像下面这样处理错误(因为async函数执行后返回一个promise)
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {reject('error')}, ms); //reject模拟出错,返回error
});
}
async function asyncPrint(ms) {
console.log('start');
await timeout(ms)
console.log('end'); //这句代码不会被执行了
}
asyncPrint(1000).catch(err => {
console.log(err); // 从这里捕捉到错误
});
如果你不想让错误中断后面代码的执行,可以提前截留住错误,像下面
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, ms); //reject模拟出错,返回error
});
}
async function asyncPrint(ms) {
console.log('start');
await timeout(ms).catch(err => { // 注意要用catch
console.log(err)
})
console.log('end'); //这句代码会被执行
}
asyncPrint(1000);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。