麻烦的异步操作

异步操作是 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函数后面的代码就没有机会执行了。

最近更新
01
echarts扇形模拟镜头焦距与可视化角度示意图
03-10
02
vite插件钩子
03-02
03
vite的依赖预构建
02-13
更多文章>