async.md 5.32 KB
  • 在JavaScript的世界中,所有代码都 是单线程执行的
  • 由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现
  • 异步操作会在将来的某个时间点触发一个函数调用
  • 主流的异步处理方案主要有:回调函数 (CallBack) 、 Promise 、 Generator 函数、 async/await。

回调函数

这是异步编程最基本、最直观的方式

downloadAsync("http://example.com/file.txt", (text) => {
    console.log(text);
});

Promise

解决回调嵌套层数过多的问题

downloadAsync("a.txt", (a) => {
    downloadAsync("b.txt", (b) => {
        downloadAsync("c.txt", (c) => {
            console.log("Contents: " + a + b + c);
        }, function(error) {
            console.log("Error: " + error);
        });
    }, function(error) {
        console.log("Error: " + error);
    });
}, function(error) {
    console.log("Error: " + error);
});
downloadAsync("a.txt").then(() => {
    return downloadAsync("b.txt");
}).then((b) => {
    return downloadAsync("c.txt");
}).then((c) => {
    console.log("Contents: " + a + b + c);
}).catch((error) => {
    console.log("Error: " + error);
});
var myPromise = new Promise((resolve, reject) => {
    ...  //异步操作
    if( success ) {
        resolve(value);
    } else {
        reject(error);
    }
});
var myPromise = new Promise((resolve, reject) => {
    resolve(1);
});

myPromise.then((value) => {
    console.log("第" + value + "一次异步操作成功");  //第1次异步操作成功
    return value+1;
}).then(function(value) {
    console.log("第" + value + "一次异步操作成功");  //第2次异步操作成功
});

Generator

Promise异步编程,可以很好地回避回调地狱。但Promise的问题是,不管什么样的异步操作,被Promise一包装,看上去都是一堆then,语义方面还不够清晰。因此更好的异步编程解决方案是ES6的Generator。

function* gen() {
    yield 1;
    yield 2;
    yield 3;
}
var g = gen();

console.log(g);         // Generator {}
console.log(g.next());  // { value: 1, done: false }
console.log(g.next());  // { value: 2, done: false }
console.log(g.next());  // { value: 3, done: true }
console.log(g.next());  // { value: undefined, done: true }

调用Generator只会得到一个遍历器对象,仅此而已。并不会执行Generator函数。上例中var g = gen();,变量g是一个遍历器对象,即一个指向内部状态的指针对象,用于之后遍历yield定义的内部状态。

有了遍历器对象g之后,就可以使用next方法使指针依次移向下一个状态,即让函数从开头或上一次暂停的地方开始执行,执行到下一个yield或return语句为止。虽然yeild和next本质上是遍历器对象和操作指针,但你使用时可以将它们简单地理解为:

Generator是分段执行的函数。yeild是暂停的标记。next用于继续执行。

自动遍历

function* numbers () {
    yield 1;
    yield 2;
    return 3;
    yield 4;
}

//for...of
var gen1 = numbers();
for (let n of gen1) {
    console.log(n);    //1  2
}

//Array.from
console.log(Array.from(numbers()));  // [1, 2]

//扩展运算符(…)
console.log([...numbers()]);    // [1, 2]

//解构赋值
let [x, y] = numbers();
console.log(x);   //1
console.log(y);   //2

应用

function delay(time, callback){
    setTimeout(function(){
        callback("sleep "+time);
    },time);
}   

delay(1000,function(msg){
    console.log(msg);
    delay(2000,function(msg){
        console.log(msg);
    });
});
//1秒后打印出:sleep 1000
//再过2秒打印出:sleep 2000
function delay(time, callback){
    setTimeout(function(){
        callback("sleep "+time);
    },time);
}   

function run(genFunc) {
    var g = genFunc(resume);
    function resume(value) {
        g.next(value);
    }
    g.next();
}

run(function* delayedMsg(resume) {
    console.log(yield delay(1000, resume));
    console.log(yield delay(2000, resume));
});
//1秒后打印出:sleep 1000
//再过2秒打印出:sleep 2000

async/await

async函数就是Generator函数的语法糖,使得异步操作的流程更加清晰。

function delay(time){
    return new Promise(function(resolve) {
        setTimeout(function(){
            resolve("sleep "+time);
        },time);
    });
}

async function run() {
    console.log(await delay(1000));
    console.log(await delay(2000));
}

async函数的await命令后面是Promise对象(如果是原始类型的值,会自动将其转成Promise对象并立即将状态设成Resolved,效果等于同步操作)。进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。因为await命令后面是Promise对象,需要考虑rejected的情况,毕竟谁也不能断言异步操作中不会出现异常,所以最好把await命令包进try…catch中:

async function myAsyncFun() {
    try {
        await somePromise();
    } catch (err) {
        console.log(err);
    }
}

//另一种写法
async function myAsyncFun() {
    await somePromise().catch(function (err) {console.log(err);});
}