麻烦的异步操作
异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。
# 回调函数
var func1=function(callback){
console.log(1);
(callback && typeof(callback)==='function') && callback();
}
func1(func2);
var func2=function(){
console.log(2);
}
最常见的例子ajax
$.ajax({
url:"/getmsg",
type: 'GET',
dataType: 'json',
success: function(ret) {
if (ret && ret.status) {
//
}
},
error: function(xhr) {
//
}
})
模拟回调函数
function mockAsync(callbackb, time) {
console.log("do something.");
setTimeout(()=>{
callback();
}, time)
}
比如我们要串行执行一些事情
mockAsync(()=>{
console.log("mock async 1")
mockAsync(()=>{
console.log("mock async 2")
mockAsync(()=>{
console.log("mock async 3")
mockAsync(()=>{
console.log("mock async 4")
}, 1000)
}, 1000)
}, 1000)
}, 1000)
通过回调实现异步,优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),使得程序结构混乱、流程难以追踪(尤其是回调函数嵌套的情况)
# Promise
promise实现ajax
var getJSON=function(url){
var promise=new Promise(function(resolve,reject){
var client=new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange=handler;
client.responseType="json";
client.setRequestHeader("Accept","application/json");
client.send();
function handler(){
if(this.readyState!=4){
return;
}
if(this.status==200){
resolve(this.response);
}else{
reject(new Error(this.statusText));
}
}
});
return promise;
}
getJSON('/posts.json').then(function(json){
console.log('Contents: '+json);
},function(error){
console.error(error)
})
Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。promise本身并没有解决回调地狱的问题,它的本质仍然是传递回调,例如上面的例子,用promise写是这样的
function usePromise(time){
console.log("do something.......");
return new Promise((resolve, reject)=>{
console.log("11111");
setTimeout(()=>{
console.log("after async.......")
// resolve(result);
}, time)
});
}
usePromise(1000).then(()=>{
console.log("use promise 1");
return usePromise(1000);
}).then(()=>{
console.log("use promise 2");
return usePromise(1000);
}).then(()=>{
console.log("use promise 3");
return usePromise(1000);
}).then(()=>{
console.log("use promise 4");
})
# Generator 函数
Generator改变了异步的实现方式,generator可以将异步的代码以同步的方式来实现,很好地实现了异步操作的流程控制。
function* useGenerator(){
yield console.log("use generator 1");
yield console.log("use generator 2");
yield console.log("use generator 3");
yield console.log("use generator 4");
}
generator函数只有调用next语句才会执行,每次执行到yield语句为止,然后在需要的时候再次调用next语句,从上次结束的位置继续执行。
function delay(time) {
console.log("in delay...");
return new Promise((resolve)=>{
setTimeout(()=>{
console.log("will resolve");
resolve("aaa");
}, time);
});
}
function* useGenerator1(){
console.log("use generator 1")
let a = yield delay(1000);
console.log("use generator 2")
let b = yield delay(1000);
console.log("use generator 3")
let c = yield delay(1000);
console.log("use generator 4")
let d = yield delay(1000);
}
// 调用方式
let iter = useGenerator1();
let result = iter.next();
console.log("before async.....");
result.value.then(()=>{
console.log("after async.....2");
iter.next();
})
Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针
next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
function* gen(){
var url = 'https://api.xxxxx.com/users/';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
# async
async和await是ES2017的内容,他们可以看做完全是generator函数的语法糖,async相当于*, await相当于yield。不同的是aync函数会自动执行
async function useAsync(){
console.log("use generator 1")
await delay(1000).then(()=>{
});
console.log("use generator 2")
await delay(1000);
console.log("use generator 3")
await delay(1000);
console.log("use generator 4")
await delay(1000);
}
console.log("before async.....");
useAsync().then((result)=>{
console.log("after async....."+ result); //没有返回值,所以结果是undefined
}).catch();
async函数返回的是一个promise,它的then函数回调参数就是async函数return的值,如果代码发生异常或者await的promise进入reject状态,则返回的promise也立即进入reject状态。
正常情况下await后面是一个promise对象,如果不是,则会转为一个立即resolve的promise对象。
多个await的promise,相当于promise.all(),也就是需要所有的promise都resolve之后,整体才进入resolve状态,任何一个进入reject,则立即进入reject。注意的是此时async函数后面的代码就没有机会执行了。