Javascript语言的执行环境是“单线程”。javascript
单线程: 一次只能完成一个任务。若是有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。html
单线程的好处是执行环境简单,坏处是在一些耗时的任务上会堵塞进程。好比读取一个大文件,线程卡在这个任务上,形成页面卡死。java
那如何在读取文件的同时,又能查看图片,作一些其余的事呢?编程
这就到了“同步”和“异步”之争:
同步:后一个任务等待前一个任务结束,而后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
异步:每个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,因此程序的执行顺序与任务的排列顺序是不一致的、异步的。promise
举个栗子:
去餐厅吃饭
同步:你们依次排队,前一我的付完钱,等待领取食物。。。漫长等待。。。食物作好了,后一我的跟进。
异步:你们依次排队,前一我的付完钱,服务员给他一个餐牌,后一我的跟进。餐牌对应的食物作好了,再去领取。异步
如下总结了“异步编程”的4种方式:async
回调函数:异步编程的最基本的方式。模块化
下面经过样例做为演示:咱们定义三个方法“作饭”(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),流程会很混乱,并且每一个任务只能指定一个回调函数。
事件监听:采用事件驱动模式,任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
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),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰
发布/订阅:又称“观察者模式”。咱们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其余任务能够向信号中心"订阅"(subscribe)这个信号,从而知道何时本身能够开始执行。
和上一个“事件监听”很相似,本人对这种模式理解有限,这里就不列代码误导你们了。
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('好像出什么问题了'); });
优势:
Promise的功能不单单只上面用到的,诸如其余all(), race()之类,限于篇幅,你们能够翻看其余文章查看。
参考文章:Javascript异步编程
参考文章:Promise使用详解