概述
单线程:JS执行环境中负责执行代码的线程只有一个 (一个人执行代码,任务排队执行) 优点:安全、简单 缺点:耗时任务等待结束,导致程序执行拖延,造成假死
- 同步模式 synchronous
- 异步模式 asynchronous
同步模式
特点:排队执行 JS内部有一个调用栈call-stack,执行代码时,会首先压入一个匿名的函数(anonymous),便开始逐行执行每行的代码
- 函数的声明并不会调用 当程序运行结束的时候,调用栈会被清空
JS在工作中维护了一个正在执行工作表,记录我们正在做的事情,被清空,就结束
缺点:某行代码执行过长,会延迟,称为堵塞
异步模式
- 耗时任务开始便马上执行下一个任务
- 逻辑一般由回调函数的方式定义 特点:代码执行顺序混乱
(以下没有先后执行顺序!) 内部API环境(web环境就是web APIs),异步函数一般会放入此处,当异步函数运行完成时,会将其送入消息队列中 消息队列Queue(回调队列)中的任务会排队等待事件循环 Event Loop监听调用栈和消息队列,如果调用栈结束,事件循环会从消息队列中取出第一个回调函数压入调用栈call stack 调用栈接收异步任务时又开始新的一轮调用,但也同时执行手头上的任务
同步和异步的差别:运行环境提供的API是否以同步还是异步模式的方式工作
回调函数
所有异步编程方案的根基
定义:由调用者定义,交给执行者执行的函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
缺点:复制的异步流程会使用大量的回调函数,会导致回调地狱问题
回调函数补充
- 回调函数在使用上是把一个函数当成参数传给另一个函数,在另一个函数中作为返回结果
function outPutPhone(name) {
console.log(name + "电话:10000");
}
//使用回调函数的函数
function searchInfo(name, callback) {
callback(name);
}
//=>将outPutPhone作为参数传递
searchInfo("tom", outPutPhone);
Promise
概述
Promise物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值一個
Promise物件處於以下幾種狀態:
- 擱置(pending):初始狀態,不是 fulfilled 與 rejected。
- 實現(fulfilled):表示操作成功地完成。
- 拒絕(rejected):表示操作失敗了。
一种更优的异步编程统一方案
表示一个异步任务究竟是成功还是失败,内部对外界进行的承诺
graph LR
promise-->pendind
pendind-->fulfilled
pendind-->Rejected
fulfilled-->onFulfilled
Rejected-->onRejected
好处
- 将复杂的异步处理轻松地进行模式化
- 代码更清晰
- 异常处理更方便
- 代码链式操作,爽!
基本用法
then()方法回傳一個Promise物件。它接收兩個引數:Promise在成功及失敗情況時的回呼函式语法:p.then(onFulfilled[, onRejected]);
const promise = new Promise(function (resolve, reject) {
// 这里用于“兑现”承诺
// resolve(100) // 承诺达成
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
console.log('end')//被率先打印
使用案例
ajax和promise结合:
// Promise 方式的 AJAX
function ajax (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('/api/foo.json').then(function (res) {
console.log(res)
}, function (error) {
console.log(error)
})
AJAX补充知识
定义:JAX 代表 Asynchronous JavaScript And XML,即非同步 JavaScript 及 XML
通俗:AJAX 使用XMLHttpRequest物件來與伺服器進行通訊
作用:AJAX可以传送并接收多种格式的咨询,JSON XML HTML等
常见误区
-
在ajax请求中,如果有多个请求嵌套,还是会出现回调地狱,Promise就没有意义,增加了复杂度,还不如使用回调函数
ajax('/api/urls.json').then(function (urls) { ajax(urls.users).then(function (users) { ajax(urls.users).then(function (users) { ajax(urls.users).then(function (users) { ajax(urls.users).then(function (users) { }) }) }) }) }) -
面对嵌套问题,应该借助于Promise then方法链式调用的特点处理
链式调用
Promise链条
//链式调用then方法
ajax('/api/users.json')
.then(function (value) {
console.log(1111)
return ajax('/api/urls.json')//ajax执行完毕后会自动执行下一个then
})
.then(function (value) {
console.log(2222)
console.log(value)
return ajax('/api/urls.json')
})
.then(function (value) {
console.log(3333)
})
.then(function (value) {
console.log(4444,value)//value为空值
return 'foo'
})
.then(function (value) {
console.log(5555)
console.log(value)//拿到的是上一个then返回的值foo
})
/**
* 每个then方法为上一个then方法返回的promise对象去添加状态明确过后的回调
* 每个then方法返回的是一个全新的promise对象
*/
总结:
-
Promise对象的then方法会返回一个全新的Promise对象
-
后面的then方法是在为上一个then返回的Pormise注册回调
-
前面then方法中回调函数的返回值会作为后面then方法回调的参数
-
如果回调中返回的是Promise,那后面then方法的回调会等待它的结束
异常处理
手动抛出一个异常throw new Error(),onRejected也可以捕获
ajax('/api/users11.json')
.then(function onFulfilled (value) {
console.log('onFulfilled', value)
return ajax('/error-url')
//此时注册在上一个then里的onRejected就无法捕获异常
}, function onRejected (error) {
console.log('onRejected', error)
})
-
同时注册的 onRejected 只是给当前 Promise 对象注册的失败回调,它只能捕获到当前 Promise 对象的异常
-
使用 catch 注册失败回调是更常见的
ajax('/api/users11.json') .then(function onFulfilled (value) { console.log('onFulfilled', value) return ajax('/error-url') //此时onRejected可以捕获异常 }) .catch(function onRejected (error) { console.log('onRejected', error) }) //then(onRejected) 实际上就相当于 then(undefined, onRejected) -
分析:因为 Promise 链条上的任何一个异常都会被一直向后传递,直至被捕获
-
分开注册的 onRejected 相当于给整个 Promise 链条注册失败回调
-
全局也可以注册unhandledrejection事件处理没有手动捕获的异常,process内注册。但编程习惯应该在每个代码中明确捕获每个可能的异常。
静态方法
**Promise.resolve()**快速把值转化成一个对象
此函数将类promise对象的多层嵌套展平
-
如果这个值是一个 promise ,那么将返回这个 promise
-
如果这个值是thenable(即带有**"then"**方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态
-
否则返回的promise将以此值完成
//示例
Promise.resolve('foo')
.then(function (value) {
console.log(value)
})
//等价于
new Promise(function (resolve, reject) {
resolve('foo')
})
- 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回
var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)
console.log(promise,promise2)
console.log(promise === promise2)
- 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,Promise.resolve 会将这个对象作为 Promise 执行
Promise.resolve({
then: function (onFulfilled, onRejected) {
onFulfilled('foo')
}//它是一个thenable的对象
})
.then(function (value) {
console.log(value)
})
**Promise.reject()**会创建一个一定失败的Promise对象
无论传入什么参数,都会作为Promise失败的理由
并行处理
Promise.all()可以合并多个Promise对象,等待所有任务结束后才会结束
Promise.race()可以合并多个Promise对象,只会等待第一个结束的任务
执行时序
微任务
//微任务
console.log('s1')
setTimeout(() => {
console.log('setTime')
}, 0)
//会立即进入到队列中排序,因为时间设置为0,等待一轮执行
Promise.resolve()
.then(() => {
console.log('p1')
})
.then(() => {
console.log('p2')
})
.then(() => {
console.log('p3')
})
console.log('s2')
//s1 s2 p1 p2 p3 setTime
//回调依次往下执行
宏任务
回调队列中的任务称之为宏任务
- 宏任务在执行过程中可以临时加上一些额外的需求,可以选择作为一个新的宏任务进到队列中排队
- 上述代码中,setTimeout就是作为宏任务进入到队列中排队
- 但也可以作为当前任务的微任务,直接在当前任务结束后立即执行
- Promise的回调作为微任务执行,会在==本轮调用结束的末尾==去自动执行
微任务优点:提高整体的响应能力
绝大多数异步调用都是作为宏任务执行的
Promise & MutationObserver / process.nextTick 都作为微任务
Generator
概念
ES2015提供,生成器函数
- 生成器函数会自动返回一个生成器对象,调用它的next方法才会让这个函数体开始执行
- 一旦中途遇到yield关键字,便会暂停执行,继续next便会继续执行
function * foo () {
console.log('start')
try{
const res = yield 'foo'
//向外返回一个foo值
//yield暂停了函数执行,直到外界下一次去调用generator.next()
console.log(res)//res = bar
}catch(e){
console.log(e)
//输出error
}
}
const generator = foo()
//generator就是生成器对象
const result = generator.next()
//手动调用next方法才会开始执行函数的函数体
console.log(result)
//可以拿到yield返回的值,也可以拿到一个done值,表示了生成器是否已经执行完了
generator.next('bar')
//如果调用了生成器的next传了参,那么这个参数会作为yield的返回值
generator.throw(new Error('error'))
//可以对生成器内部抛出一个异常
异步方案
借助yield
function * main () {
try {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls11.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
const result = g.next()
//result.value是一个promise对象,可以调用then处理请求结果
result.value.then(data => {
const result2 = g.next(data)
//继续请求next去进行yeild下面的代码,并且把得到的数据data传递回去
if (result2.done) return//若生成器已经结束,则没有必要继续
result2.value.then(data => {
const result3 = g.next(data)//递归调用
if (result3.done) return
result3.value.then(data => {
g.next(data)
})
})
})
递归无止尽地调用yeild:
function * main () {
try {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls11.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
function co (generator) {//封装成为一个公共的函数
const g = generator()
function handleResult (result) {
if (result.done) return // 生成器函数结束
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)
特点:使异步调用回归扁平化
Async/Await语法糖
将 * 函数替换成async,将yeild替换成await
本文首发于我的GitHub博客,其它博客同步更新。