異步編程
一、JS是單線程語言
JS執行環境找那個負責執行代碼的線程只有一個
執行任務的模式有兩種:同步模式、異步模式。
二、Promise
1. 基本使用
// Promise 基本演示
const promise = new Promise(function (resolve, reject) {
// 這裏用於兌現承諾
// resolve(100) // 承諾達成
reject(new Error('promise rejected')) // 承諾失敗
})
promise.then(function (value) {
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
console.log('end') // 先打印出end,再打印Error
2. 通過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/users.json2').then(function (res) {
console.log(res)
}, function (error) {
console.log(error)
})
3. Promise通過鏈式調用避免回調嵌套
- Promise對象的then方法會返回一個全新的Promise對象
- 後面的then方法就是在爲上一個then返回的Promise註冊回調
- 前面then方法中回調函數的返回值會作爲後面then方法回調的參數
- 如果回調返回的是Promise,那後面then方法的回調會等待它的結束
function ajax(url) {
return new Promise(function(resolve, reject) {
// foo()
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()
})
}
var promise = ajax('/api/users.json')
// then 方法返回一個全新的promise對象
var promise2 = promise.then(function (res) {
console.log(res)
}, function (error) {
console.log(error)
})
console.log(promise2 === promise) // false
// 每一個then方法都是在爲上一個then方法添加狀態明確過後的回調
ajax('/api/users.json')
.then(function (value) {
console.log(111)
return ajax('/api/users.json')
}) // => Promise
.then(function (value) {
console.log('yi', value)
console.log(222)
return 'foo'
}) // => Promise
.then(function (value) {
console.log(333)
console.log('jal', value)
}) // => Promise
.then(function (value) {
console.log(444)
console.log('ji', value)
}).catch(function onRejected(error) {
console.log('onRejected', error)
})
其中catch也是then的別名
.catch(function onRejected(error) {
console.log('onRejected', error)
})
// 就相當於
.then(undefined, function (value) {
console.log(444)
console.log('ji', value)
})
then中的第二個參數是reject函數,catch中的參數也是reject函數,但是作用不太相同,then中的reject不能捕獲到第一個參數中的resolve中的異常,但是catch由於鏈式作用,能捕獲到前面任意處的異常
ajax('/api/users.json')
.then(function (value) {
console.log(111)
return ajax('/api/users2.json') // 這個異常無法捕獲
}, function onRejected (e){
console.log('reject', e)
})
推薦使用catch捕獲異常,可以捕獲整個promise鏈條上的異常:
ajax('/api/users.json')
.then(function (value) {
console.log(111)
return ajax('/api/users2.json')
}).catch(function onRejected (e){
console.log('reject', e)
})
還可以全局捕獲異常:
瀏覽器中,在window對象上註冊事件:
window.addEventListener('unhandledrejection', event => {
const {reason, promise} = event
console.log(reason, promise)
// reason => Promise 失敗原因,一般是一個錯誤對象
// promise => 出現異常的Promise對象
event.preventDefault()
}, false)
node中:
process.on('unhandledRejection', (reason, promise) => {
const {reason, promise} = event
console.log(reason, promise)
// reason => Promise 失敗原因,一般是一個錯誤對象
// promise => 出現異常的Promise對象
})
儘量在代碼中明確捕獲每一個可能的異常,而不是丟給全局處理。
5. Promise靜態方法
-
Promise.resolve() 快速的把一個值轉化爲Promise對象
Promise.resolve('foo') .then(function (value) { console.log(value) // 'foo' }) // Promise.resolve('foo') 等價於 new Promise(function (resolve, reject){ resolve('foo') })
var promise = ajax('/api/users.json') var promise2 = Promise.resolve(promise) console.log(promise === promise2) // true
// 帶有then方法的對象,就是實現了thenable接口, 可以被then的對象 Promise.resolve({ then: function (onFulfilled, onRejected) { onFulfilled('foo') } }).then(function (value){ console.log(value) // foo })
-
Promise.reject(err) 傳入的對象爲失敗的原因
Promise.reject('anything') .catch(function (err) { console.log(err) // anything })
6. Promise並行執行
-
Promise.all() 等待所有任務成功結束了,纔算結束
var promise = Promise.all([ ajax('/api/users.json'), ajax('/api/posts.json'), ]) // 只有promise裏面的每一個任務都執行成功了才進入resolve // 其中任何一個失敗了,都會進入catch promise.then(function (values) { console.log(values) // 返回一個數組 // Array(2) // 0: // username: "yibo" // __proto__: Object // 1: // name: "jiailing" }).catch(function(err){ console.log(err) }) ajax('/api/urls.json') .then( value => { const urls = Object.values(value) const tasks = urls.map(url => ajax(url)) return Promise.all(tasks) }) .then(values => { console.log(values) })
-
Promise.race() 只會等待第一個結束的任務
const request = ajax('/api/posts.json') const timeout = new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('timeout')) }, 500); }) Promise.race([ request, timeout ]) .then(value=>{ console.log(value) }) .catch(error=>{ console.log(error) }) // 實現ajax請求超時控制的一種方式
7. 微任務
即使Promise中沒有任何異步操作,它的回調函數仍然會進入到回調隊列中排隊。必須等待所有同步代碼執行完畢後,Promise中的代碼纔會被調用。
JS回調隊列中的任務稱之爲【宏任務】,而宏任務執行過程中可以臨時加上一些額外需求,可以選擇作爲一個新的宏任務進到隊列中排隊(如setTimeout),也可以作爲當前任務的【微任務】,直接在當前任務結束後立即執行。
Promise的回調會作爲微任務執行。微任務的目的是爲了提高整體的響應能力,目前絕大多數異步調用都是作爲宏任務執行,Promise 、MutationObserver、process.nextTick 是作爲微任務在本輪調用的末尾執行。
console.log('global start') // 第一個打印
Promise.resolve()
.then(()=>{
console.log('promise')// 第3個打印
})
.then(()=>{
console.log('promise 2')// 第4個打印
})
.then(()=>{
console.log('promise 3')// 第5個打印
})
console.log('global end')// 第2個打印
setTimeout屬於宏任務
console.log('global start') // 第一個打印
setTimeout(() => {
console.log('last') // 最後調用
}, 0);
Promise.resolve()
.then(()=>{
console.log('promise')// 第3個打印
})
.then(()=>{
console.log('promise 2')// 第4個打印
})
.then(()=>{
console.log('promise 3')// 第5個打印
})
console.log('global end')// 第2個打印
三、Generator異步方案
1. Generator的基本使用
生成器函數會返回一個生成器對象,調用這個生成器對象的next方法,纔會讓函數體執行,一旦遇到了yield關鍵詞,函數的執行則會暫停下來,yield後面的值作爲next函數的結果返回,如果繼續調用函數的next函數,則會再上一次暫停的位置繼續執行,知道函數體執行完畢,next返回的對象的done就變成了true
function * fn () {
console.log(111)
yield 100
console.log(222)
yield 200
console.log(333)
yield 300
}
const generator = fn()
console.log(generator.next())
// 111
// { value: 100, done: false }
console.log(generator.next())
// 222
// { value: 200, done: false }
console.log(generator.next())
// 333
// { value: 300, done: false }
2. Generator實現異步
注意:generator.next(value)中,next傳入的參數會作爲上一次yield的返回值。
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()
})
}
// 生成器函數
function * main () {
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/urls.json')
console.log(urls)
}
// 調用生成器函數得到一個生成器對象
const generator = main()
// 遞歸實現generator.next()的調用,直到done爲true終止
function dfs(value) {
const result = generator.next(value)
if(result.done) return
result.value.then(data=>{
console.log(data)
dfs(data)
})
}
dfs()
// 打印結果
// Generator實現異步.js:35 {username: "yibo"}
// Generator實現異步.js:19 {username: "yibo"}
// Generator實現異步.js:35 {posts: "jiailing"}
// Generator實現異步.js:22 {posts: "jiailing"}
// Generator實現異步.js:35 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator實現異步.js:25 {posts: "/api/posts.json", users: "/api/users.json"}
封裝生成器函數執行器co
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()
})
}
// 生成器函數
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/urls.json')
console.log(urls)
} catch(e) {
// 如果生成器函數中,發生了異常,會被生成器對象的throw方法捕獲
console.log(e)
}
}
// 封裝了一個生成器函數執行器
function co(main) {
// 調用生成器函數得到一個生成器對象
const generator = main()
// 遞歸實現generator.next()的調用,直到done爲true終止
function handleResult(result) {
if(result.done) return
result.value.then(data=>{
console.log(data)
handleResult(generator.next(data))
}, error => {
g.throw(error)
})
}
handleResult(generator.next())
}
co(main)
// Generator實現異步.js:42 {username: "yibo"}
// Generator實現異步.js:20 {username: "yibo"}
// Generator實現異步.js:42 {posts: "jiailing"}
// Generator實現異步.js:23 {posts: "jiailing"}
// Generator實現異步.js:42 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator實現異步.js:26 {posts: "/api/posts.json", users: "/api/users.json"}
四、Async/Await 語法糖
await關鍵詞只能出現在async函數中。
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()
})
}
async function main () {
try {
const users = await ajax('/api/users.json')
console.log(users)
const posts = await ajax('/api/posts.json')
console.log(posts)
const urls = await ajax('/api/urls.json')
console.log(urls)
} catch(e) {
console.log(e)
}
}
main()
// async-await.js:20 {username: "yibo"}
// async-await.js:23 {posts: "jiailing"}
// async-await.js:26 {posts: "/api/posts.json", users: "/api/users.json"}