javascript异步编程总结

前言

Javascript语言的执行环境是“单线程”。javascript

单线程: 一次只能完成一个任务。若是有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。html

单线程的好处是执行环境简单,坏处是在一些耗时的任务上会堵塞进程。好比读取一个大文件,线程卡在这个任务上,形成页面卡死。java

那如何在读取文件的同时,又能查看图片,作一些其余的事呢?编程

这就到了“同步”和“异步”之争:
同步:后一个任务等待前一个任务结束,而后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
异步:每个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,因此程序的执行顺序与任务的排列顺序是不一致的、异步的。promise

举个栗子:
去餐厅吃饭
同步:你们依次排队,前一我的付完钱,等待领取食物。。。漫长等待。。。食物作好了,后一我的跟进。
异步:你们依次排队,前一我的付完钱,服务员给他一个餐牌,后一我的跟进。餐牌对应的食物作好了,再去领取。异步

如下总结了“异步编程”的4种方式:async

1. 回调函数

回调函数:异步编程的最基本的方式。模块化

下面经过样例做为演示:咱们定义三个方法“作饭”(cook)、“吃饭”(eat),“洗碗”(wash)三个方法,它们是层层依赖的关系,下一步的的操做须要使用上一部操做的结果(这里使用 setTimeout 模拟异步操做)。异步编程

// 作饭
function cook() {
    console.log('开始作饭...');

    sleep(2000);   // 等待2秒

    console.log('作饭完毕');
}

// 吃饭
function eat() {
    console.log('开始吃饭...');

    sleep(2000);   // 等待2秒

    console.log('吃饭完毕');
}

// 洗碗
function wash() {
    console.log('开始洗碗...');

    sleep(2000);   // 等待2秒

    console.log('洗碗完毕');
}

// 阻塞等待(毫秒)
function sleep(delay) {
    let start = (new Date()).getTime();

    while((new Date()).getTime() - start < delay) {
        continue;
    }
}

cook();
eat();
wash();

// 开始作饭...
// 作饭完毕
// 开始吃饭...
// 吃饭完毕
// 开始洗碗...
// 洗碗完毕

上面是“同步”的写法,下面咱们改写成“异步”:回调函数函数

// 作饭
function cook(callback) {
    console.log('开始作饭...');

    setTimeout(function() {
        console.log('作饭完毕');

        // 这里是回调,执行吃饭的方法
        callback();

    }, 2000);
}

// 吃饭
function eat(callback) {
    console.log('开始吃饭...');

    setTimeout(function() {
        console.log('吃饭完毕');

        // 这里是回调,执行洗碗的方法
        callback();
        
    }, 2000);
}

// 洗碗
function wash() {
    console.log('开始洗碗...');

    setTimeout(function() {
        console.log('洗碗完毕');

        // 洗碗以后的其余动做,这里就不写了

    }, 2000);
}

cook(function() {
    eat(function() {
        wash();
    })
});

// 开始作饭...
// 作饭完毕
// 开始吃饭...
// 吃饭完毕
// 开始洗碗...
// 洗碗完毕

回调函数的优势是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,并且每一个任务只能指定一个回调函数。

2. 事件监听

事件监听:采用事件驱动模式,任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

let events = require('events');
let eventEmitter = new events.EventEmitter();

// 作饭
var cook = function() {
    console.log('开始作饭...');

    setTimeout(function() {
        console.log('作饭完毕');

        // 执行eat事件
        eventEmitter.emit('eatEvent');

    }, 2000);
}

// 吃饭
var eat = function() {
    console.log('开始吃饭...');

    setTimeout(function() {
        console.log('吃饭完毕');

        // 执行wash事件
        eventEmitter.emit('washEvent');

    }, 2000);
}

// 洗碗
var wash = function() {
    console.log('开始洗碗...');

    setTimeout(function() {
        console.log('洗碗完毕');

        // 洗碗以后的其余动做,这里就不写了

    }, 2000);
}

// 绑定cook事件
eventEmitter.on('cookEvent', cook);
// 绑定eat事件
eventEmitter.on('eatEvent', eat);
// 绑定wash事件
eventEmitter.on('washEvent', wash);

// 执行cook事件
eventEmitter.emit('cookEvent');

// 开始作饭...
// 作饭完毕
// 开始吃饭...
// 吃饭完毕
// 开始洗碗...
// 洗碗完毕

这种方法的优势是比较容易理解,能够绑定多个事件,每一个事件能够指定多个回调函数,并且能够"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰

3. 发布/订阅

发布/订阅:又称“观察者模式”。咱们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其余任务能够向信号中心"订阅"(subscribe)这个信号,从而知道何时本身能够开始执行。

和上一个“事件监听”很相似,本人对这种模式理解有限,这里就不列代码误导你们了。

4. Promise

Promise: 由 CommonJS 小组的成员在 Promise/A 规范中提出,目的是为异步编程提供统一接口。

根据 Promise/A 规范,promise 是一个对象,只须要 then 这一个方法。

// 作饭
function cook() {
    console.log('开始作饭...');

    let promise = new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('作饭完毕');
    
            resolve();
    
        }, 2000);
    });

    return promise;
}

// 吃饭
function eat(callback) {
    console.log('开始吃饭...');

    let promise = new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('吃饭完毕');
    
            resolve();
            
        }, 2000);
    });

    return promise;
}

// 洗碗
function wash() {
    console.log('开始洗碗...');

    let promise = new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('洗碗完毕');
    
            // 洗碗以后的其余动做,这里就不写了
            resolve();
    
        }, 2000);
    });

    return promise;
}

cook()
.then(function() {
    return eat();
})
.then(function() {
    return wash();
})
.then(function() {
    console.log('结束...');
})
.catch(function() {
    console.log('好像出什么问题了'); 
});

优势:

  1. 回调函数变成了链式写法,程序的流程能够看得很清楚,并且有一整套的配套方法,能够实现许多强大的功能。解决回调地狱(Callback Hell)问题
  2. 更好地进行错误捕获

Promise的功能不单单只上面用到的,诸如其余all(), race()之类,限于篇幅,你们能够翻看其余文章查看。

结尾

参考文章:Javascript异步编程
参考文章:Promise使用详解

相关文章
相关标签/搜索