javascript的执行分为三个部分:运行时,事件循环,js引擎。运行时提供了诸如注入全局API(dom, setTimeout之类)这样的功能。js引擎负责代码编译执行,包括内存管理。以前写了一篇关于javascript内存管理的文章,具体可见 javascript内存管理以及三种常见的内存泄漏
javascript执行示意图以下所示:
javascript
做为前端工程师,咱们都知道javascript是单线程的。所谓单线程,就是在同一时间咱们只能响应一个操做,这带来的问题是若是某个操做极为耗时,好比处理复杂的图像运算或者等待服务器返回数据的过程,典型的场景以下所示:前端
// This is assuming that you're using jQuery
jQuery.ajax({
url: 'https://api.example.com/endpoint',
success: function(response) {
// This is your callback.
},
async: false // And this is a terrible idea
});
复制代码
这个ajax请求以同步的方式进行调用,在接口返回数据以前javascript线程都会处于被占用的状态,会致使当前页面在success函数执行完成前不能响应用户的任何操做。若是这个过程持续时间过长,就会直接形成页面处于假死状态
java
让咱们来看以下这段代码:jquery
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
复制代码
执行这段代码,咱们能够看下调用栈和任务队列中都发生了什么git
随着javascript语言的发展,针对异步流程控制也有了愈来愈多的解决方案,依照历史发展的车辙,主要有四种:程序员
// 以jquery中的请求为例
$.ajax({
url: 'xx1',
success: function () {
console.log('1');
$.ajax({
url: 'xx2',
success: function () {
console.log('2')
}
})
}
})
复制代码
在上述代码中咱们经过在xx1请求完成的回调函数中发起xx2的请求这种回调嵌套的方式来实现两个异步任务的执行顺序控制。这种回调函数的方式在es6出现以前是应用最为普遍的实现方案,可是其缺点也很明显,若是咱们有多个异步任务须要依次执行,那么就会致使很是深的嵌套层次,形成回调地狱,下降代码可读性。es6
var ajax1 = function () {
return new Promise(function (resolve, reject) {
$.ajax({
url: 'xx1',
success: function () {
console.log('1')
resolve()
}
})
})
}
ajax1().then(() => {
$.ajax({
url: 'xx1',
success: function () {
console.log('2')
}
})
})
复制代码
promise经过then方法的链式调用将须要按顺序执行的异步任务串起来,在代码可读性方面有很大提高。
究其实现原理,Promise是一个构造函数,它有三个状态,分别是pending, fullfilled,rejected,构造函数接受一个回调做为参数,在该回调函数中执行异步任务,而后经过resolve或者reject将promise的状态由pending置为fullfilled或者rejected。
Promise的原型对象上定义了then方法,该方法的做用是将传递给它的函数压入到resolve或者reject状态对应的任务数组中,当promise的状态发生改变时依次执行与状态相对应的数组中的回调函数,此外,promise在其原型上还提供了catch方法来处理执行过程当中遇到的异常。
Promise函数自己也有两个属性race,all。race,all都接受一个promise实例数组做为参数,二者的区别在于前者只要数组中的某个promise任务率先执行完成就会直接调用回调数组中的函数,后者则须要等待所有promise任务执行完成。
一个mini的promise代码实现示例以下所示:github
function Promise (fn) {
this.status = 'pending';
this.resolveCallbacks = [];
this.rejectCallbacks = [];
let _this = this
function resolve (data) {
_this.status = 'fullfilled'
_this.resolveCallbacks.forEach((item) => {
if (typeof item === 'function') {
item.call(this, data)
}
})
}
function reject (error) {
_this.status = 'rejected'
_this.rejectCallbacks.forEach((item) => {
if (typeof item === 'function') {
item.call(this, error)
}
})
}
fn.call(this, resolve, reject)
}
Promise.prototype.then = function (resolveCb, rejectCb) {
this.resolveCallbacks.push(resolveCb)
this.rejectCallbacks.push(rejectCb)
}
Promise.prototype.catch = function (rejectCb) {
this.rejectCallbacks.push(rejectCb)
}
Promise.race = function (promiseArrays) {
let cbs = [], theIndex
if (promiseArrays.some((item, index) => {
return theIndex = index && item.status === 'fullfilled'
})){
cbs.forEach((item) => {
item.call(this, promiseArrays[theIndex])
})
}
return {
then (fn) {
cbs.push(fn)
return this
}
}
}
Promise.all = function (promiseArrays) {
let cbs = []
if (promiseArrays.every((item) => {
return item.status === 'fullfilled'
})) {
cbs.forEach((item) => {
item.call(this)
})
}
return {
then (fn) {
cbs.push(fn)
return this
}
}
}
复制代码
以上是我对promise的一个很是简短的实现,主要是为了说明promise的封装运行原理,它对异步任务的管理是如何实现的。web
function* ajaxManage () {
yield $.ajax({
url: 'xx1',
success: function () {
console.log('1')
}
})
yield $.ajax({
url: 'xx2',
success: function () {
console.log('2')
}
})
return 'ending'
}
var manage = ajaxManage()
manage.next()
manage.next()
manage.next() // return {value: 'ending', done: true}
复制代码
在上述示例中咱们定义了ajaxManage这个generator函数,可是当咱们调用该函数时他并无真正的执行其内部逻辑,而是会返回一个迭代器对象,generator函数的执行与普通函数不一样,只有调用迭代器对象的next方法时才会去真正执行咱们在函数体内编写的业务逻辑,且next方法的调用只会执行单个经过yield或return关键字所定义的状态,该方法的返回值是一个含有value以及done这两个属性的对象,value属性值为当前状态值,done属性值为false表示当前不是最终状态。
咱们能够经过将异步任务定义为多个状态的方式,用generator函数的迭代器机制去管理这些异步任务的执行。这种方式虽然也是一种异步流程控制的解决方案,可是其缺陷在于咱们须要手动管理generator函数的迭代器执行,若是咱们须要控制的异步任务数量众多,那么咱们就须要屡次调用next方法,这显然也是一种不太好的开发体验。
为了解决这个问题,也有不少开发者写过一些generator函数的自动执行器,其中比较广为人知的就是著名程序员TJ Holowaychuk开发的co 模块,有兴趣的同窗能够多了解下。ajax
async function ajaxManage () {
await $.ajax({
url: 'xx1',
success: function () {
console.log('1')
}
})
await $.ajax({
url: 'xx2',
success: function () {
console.log('2')
}
})
}
ajaxManage()
复制代码
经过代码示例能够看出,async/await在写法上与generator函数是极为相近的,仅仅只是将*号替换为async,将yield替换为await,可是async/await相比generator,它自带执行器,像普通函数那样调用便可。另外一方面它更加语义化,可读性更高,它也已经获得大多数主流浏览器的支持。
// `rp` is a request-promise function.
rp(‘https://api.example.com/endpoint1').then(function(data) {
// …
});
// 使用await模式
var response = await rp(‘https://api.example.com/endpoint1');
// 错误处理
// promise的写法
function loadData() {
try { // Catches synchronous errors.
getJSON().then(function(response) {
var parsed = JSON.parse(response);
console.log(parsed);
}).catch(function(e) { // Catches asynchronous errors
console.log(e);
});
} catch(e) {
console.log(e);
}
}
// async/await处理
async function loadData() {
try {
var data = JSON.parse(await getJSON());
console.log(data);
} catch(e) {
console.log(e);
}
}
// 异步条件判断
// promise处理
function loadData() {
return getJSON()
.then(function(response) {
if (response.needsAnotherRequest) {
return makeAnotherRequest(response)
.then(function(anotherResponse) {
console.log(anotherResponse)
return anotherResponse
})
} else {
console.log(response)
return response
}
})
}
// async/await改造
async function loadData() {
var response = await getJSON();
if (response.needsAnotherRequest) {
var anotherResponse = await makeAnotherRequest(response);
console.log(anotherResponse)
return anotherResponse
} else {
console.log(response);
return response;
}
}
复制代码
// promise
function loadData() {
return callAPromise()
.then(callback1)
.then(callback2)
.then(callback3)
.then(() => {
throw new Error("boom");
})
}
loadData()
.catch(function(e) {
console.log(err);
// Error: boom at callAPromise.then.then.then.then (index.js:8:13)
});
// async/await
async function loadData() {
await callAPromise1()
await callAPromise2()
await callAPromise3()
await callAPromise4()
await callAPromise5()
throw new Error("boom");
}
loadData()
.catch(function(e) {
console.log(err);
// output
// Error: boom at loadData (index.js:7:9)
});
复制代码
事件循环是宿主环境处理javascript单线程带来的执行阻塞问题的解决方案,所谓异步,就是当事件发生时将指定的回调加入到任务队列中,等待调用栈空闲时由事件循环将其取出压入到调用栈中执行,从而达到不阻塞主线程的目的。由于异步回调的执行时机是不可预测的,因此咱们须要一种解决方案能够帮助咱们实现异步执行流程控制,本篇文章也针对这一问题分析了当前处理异步流程控制的几种方案的优缺点和实现原理。但愿能对你们有所帮助。