JavaScript(JS) 异步编程

Javascript语言将任务的执行模式分成两种:同步和异步。如果任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。这时就需要使用异步编程。

1、JavaScript(JS) 运行机制

JS 是单线程运行的,也就是两段代码不能同时运行,而是必须逐步地运行,所以在同步代码执行过程中,异步代码是不执行的。只有等同步代码执行结束后,异步代码才会被添加到事件队列中。这里就涉及到执行栈和任务队列:

同步代码是依次存放在执行栈中,遵循LIFO原则;

异步代码存放在任务队列中,任务队列又分宏任务和微任务(微任务执行优先级高于宏任务),遵循FIFO原则;

例如,

function foo(){
    console.log('start...');
    return bar();
}
function bar(){
    console.log('bar...');
}
//这里采用ES6的箭头函数、Promise函数
var promise = new Promise(function(resolve,reject){
    console.log('promise...');
    resolve();
});
promise.then(()=>console.log('promise resolve...'));
setTimeout(()=>console.log('timeout...'),0);
foo()
console.log('end...');

输出结果

promise...
start...
bar...
end...
promise resolve...
timeout...

2、异步编程常用实现方式

JavaScript(JS)异步编程常用实现方式有,常用的定时器、ajaxPromiseGeneratorasync/await

1)定时器

使用setTimeout()setInterval()定时器来实现异步。

例如,

console.log( "a" );
setTimeout(function() {
console.log( "c" )
}, 500 );
setTimeout(function() {
console.log( "d" )
}, 500 );
setTimeout(function() {
console.log( "e" )
}, 500 );
console.log( "b" );

或者

(function(i){// 闭包
        var countDown;
        // 对按钮进行操作
        function change(i) {
            var text = "我同意";
            if (i > 0) {
                text += "(" + i + "秒)";
            } else {
                $(':button').prop('disabled', false);
                // 不再调用
                clearInterval(countDown);
            }
            $(':button').val(text);
        };
        // 页面加载完毕执行
        $(function(){
            // 计时器(1秒调用一次)
            countDown = setInterval(function () {
                change(i);
                i--;
            }, 1 * 1000);// 1000毫秒
        });
    })(60);// 60s

2)异步AJAX

XMLHttpRequest 常常用于请求来自远程服务器上的 XMLJSON 数据。一个标准的 XMLHttpRequest 对象往往包含多个回调.

例如,

(function(){
            var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            var url = "authorInfo.json";
            xmlhttp.onreadystatechange = function(){
                if(xmlhttp.readyState==4){
                    if(xmlhttp.status==200){
                        console.log(xmlhttp.response);
                        //异步获取数据后再doSomething
                    }
                }
            }
            xmlhttp.open('GET',url,true);
            xmlhttp.send(null);
        })();

3)使用Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

例如,

const p = function(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('Refused the request!');
        },0);
    })
};
const p2 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(p())
        },0);
    })
};
p2().then(val => {
    console.info('Status switches to fulfilled');
    console.info(val);
}, val => {
    console.info('Status switches to reject');
    console.info(val);
});
// 输出 "Status switches to reject"
// 输出 "Refused the request!"

4)Generator

Generator 顾名思义是生成器, 使用 function* 语法和一个或多个 yield 表达式以创建一个函数即为生成器,当然它的返回值就是一个迭代器即生成器。

例如,

function* gen(x){
var y = yield x + 2;
return y;
}

Generator语法 function* name(){},一般*和函数名中间有个空格,函数体内可通过yield关键字修饰,需要注意的是,yield后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。

5)async/await

async关键字的函数,是声明异步函数,返回值是Promise对象,如果async关键字函数返回的不是Promise,会自动用Promise.resolve()包装。await等待右侧表达式的结果,这个结果是Promise对象或者其他值。如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是返回的值。

如果它等到的是一个 Promise 对象,await 就会执行,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

例如,

function myFun() {
    return new Promise(resolve => {
        setTimeout(() => resolve("cjavapy"), 2000);
    });
}
async function runAsync() {
    const result = await myFun();
    console.log(result);
}
runAsync();
console.log('end');

推荐阅读
cjavapy编程之路首页