今天說一下JS的異步編程,jS異步共三個階段:最先的callback逐層嵌套(回調地獄),後來出了Promise以後風平浪靜了,沒過兩年又有人出來嘚瑟,封裝了一下Promise,變出來一個 async+await ,這個其實就是Promise的語法糖(Promise+generator = async+await ),下面分別說下:
1.1 發佈訂閱模式,上代碼:javascript
let fs = require('fs');// 發佈 訂閱 發佈者和訂閱者是沒有依賴關係的let dep = { arr:[], on(callback){ this.arr.push(callback) }, emit(){ this.arr.forEach(item => { item(); }); }}let school = {}dep.on(function () { if (Object.keys(school).length === 2) { console.log(school); }});fs.readFile('./age.txt', 'utf8', function (err, data) { school['name'] = data; dep.emit();});fs.readFile('./name.txt', 'utf8', function (err, data) { school['age'] = data; dep.emit();});複製代碼
1.2 發佈訂閱模式,上代碼:css
// 觀察者
class Observer{ constructor(){ this.arr = []; this.val = 1; // 等待這個值更新時 觸發被觀察者的更新方法 } updateVal(val){ this.val = val; this.notify(); } notify(){ this.arr.forEach(s => s.update(this.val)); } save(s){ this.arr.push(s); }}// 被觀察者,被觀察者有一個更新的方法class Subject{ constructor() { this.small = ''; } update(val){ this.small = val; console.log(this); }}let s = new Subject(); // 一個個小的被觀察者let s2 = new Subject(); // 一個個小的被觀察者let observe = new Observer();observe.save(s);observe.save(s2);observe.updateVal(100); // 當值發生變化後 會自動觸發複製代碼
構造函數
Promise
必須接受一個函數做爲參數,咱們稱該函數爲handle
,handle
又包含resolve
和reject
兩個參數,它們是兩個函數。html
定義一個判斷一個變量是否爲函數的方法,後面會用到java
// 判斷變量否爲function
const isFunction = variable => typeof variable === 'function'
複製代碼複製代碼
首先,咱們定義一個名爲 MyPromise
的 Class
,它接受一個函數 handle
做爲參數node
class MyPromise {
constructor (handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
}
}
複製代碼複製代碼
再往下看git
Promise
對象存在如下三種狀態:github
Pending(進行中)
npm
Fulfilled(已成功)
編程
Rejected(已失敗)
redux
狀態只能由
Pending
變爲Fulfilled
或由Pending
變爲Rejected
,且狀態改變以後不會在發生變化,會一直保持這個狀態。
Promise
的值是指狀態改變時傳遞給回調函數的值
上文中
handle
函數包含resolve
和reject
兩個參數,它們是兩個函數,能夠用於改變Promise
的狀態和傳入Promise
的值
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('FULFILLED')
}, 1000)
})
複製代碼複製代碼
這裏 resolve
傳入的 "FULFILLED"
就是 Promise
的值
resolve
和 reject
resolve
: 將Promise對象的狀態從 Pending(進行中)
變爲 Fulfilled(已成功)
reject
: 將Promise對象的狀態從 Pending(進行中)
變爲 Rejected(已失敗)
resolve
和 reject
均可以傳入任意類型的值做爲實參,表示 Promise
對象成功(Fulfilled)
和失敗(Rejected)
的值瞭解了 Promise
的狀態和值,接下來,咱們爲 MyPromise
添加狀態屬性和值
首先定義三個常量,用於標記Promise對象的三種狀態
// 定義Promise的三種狀態常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
複製代碼複製代碼
再爲
MyPromise
添加狀態和值,並添加狀態改變的執行邏輯
class MyPromise {
constructor (handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加狀態
this._status = PENDING
// 添加狀態
this._value = undefined
// 執行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle時執行的函數
_resolve (val) {
if (this._status !== PENDING) return
this._status = FULFILLED
this._value = val
}
// 添加reject時執行的函數
_reject (err) {
if (this._status !== PENDING) return
this._status = REJECTED
this._value = err
}
}
複製代碼複製代碼
這樣就實現了 Promise
狀態和值的改變。下面說一說 Promise
的核心: then
方法
Promise
對象的 then
方法接受兩個參數:
promise.then(onFulfilled, onRejected)
複製代碼複製代碼
參數可選
onFulfilled
和 onRejected
都是可選參數。
onFulfilled
或 onRejected
不是函數,其必須被忽略onFulfilled
特性
若是 onFulfilled
是函數:
promise
狀態變爲成功時必須被調用,其第一個參數爲 promise
成功狀態傳入的值( resolve
執行時傳入的值)promise
狀態改變前其不可被調用onRejected
特性
若是 onRejected
是函數:
promise
狀態變爲失敗時必須被調用,其第一個參數爲 promise
失敗狀態傳入的值( reject
執行時傳入的值)promise
狀態改變前其不可被調用屢次調用
then
方法能夠被同一個 promise
對象調用屢次
promise
成功狀態時,全部 onFulfilled
需按照其註冊順序依次回調promise
失敗狀態時,全部 onRejected
需按照其註冊順序依次回調返回
then
方法必須返回一個新的 promise
對象
promise2 = promise1.then(onFulfilled, onRejected);
複製代碼複製代碼
所以 promise
支持鏈式調用
promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
複製代碼複製代碼
這裏涉及到 Promise
的執行規則,包括「值的傳遞」和「錯誤捕獲」機制:
一、若是 onFulfilled
或者 onRejected
返回一個值 x
,則運行下面的 Promise
解決過程:[[Resolve]](promise2, x)
x
不爲 Promise
,則使 x
直接做爲新返回的 Promise
對象的值, 即新的onFulfilled
或者 onRejected
函數的參數.x
爲 Promise
,這時後一個回調函數,就會等待該 Promise
對象(即 x
)的狀態發生變化,纔會被調用,而且新的 Promise
狀態和 x
的狀態相同。下面的例子用於幫助理解:
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
promise2 = promise1.then(res => {
// 返回一個普通值
return '這裏返回一個普通值'
})
promise2.then(res => {
console.log(res) //1秒後打印出:這裏返回一個普通值
})
複製代碼複製代碼
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
promise2 = promise1.then(res => {
// 返回一個Promise對象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('這裏返回一個Promise')
}, 2000)
})
})
promise2.then(res => {
console.log(res) //3秒後打印出:這裏返回一個Promise
})
複製代碼複製代碼
二、若是 onFulfilled
或者onRejected
拋出一個異常 e
,則 promise2
必須變爲失敗(Rejected)
,並返回失敗的值 e
,例如:
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
promise2 = promise1.then(res => {
throw new Error('這裏拋出一個異常e')
})
promise2.then(res => {
console.log(res)
}, err => {
console.log(err) //1秒後打印出:這裏拋出一個異常e
})
複製代碼複製代碼
三、若是onFulfilled
不是函數且 promise1
狀態爲成功(Fulfilled)
, promise2
必須變爲成功(Fulfilled)
並返回 promise1
成功的值,例如:
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
promise2 = promise1.then('這裏的onFulfilled原本是一個函數,但如今不是')
promise2.then(res => {
console.log(res) // 1秒後打印出:success
}, err => {
console.log(err)
})
複製代碼複製代碼
四、若是 onRejected
不是函數且 promise1
狀態爲失敗(Rejected)
,promise2
必須變爲失敗(Rejected)
並返回 promise1
失敗的值,例如:
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('fail')
}, 1000)
})
promise2 = promise1.then(res => res, '這裏的onRejected原本是一個函數,但如今不是')
promise2.then(res => {
console.log(res)
}, err => {
console.log(err) // 1秒後打印出:fail
})
複製代碼複製代碼
根據上面的規則,咱們來爲 完善 MyPromise
修改 constructor
: 增長執行隊列
因爲 then
方法支持屢次調用,咱們能夠維護兩個數組,將每次 then
方法註冊時的回調函數添加到數組中,等待執行
constructor (handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加狀態
this._status = PENDING
// 添加狀態
this._value = undefined
// 添加成功回調函數隊列
this._fulfilledQueues = []
// 添加失敗回調函數隊列
this._rejectedQueues = []
// 執行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
複製代碼複製代碼
添加then方法
首先,then
返回一個新的 Promise
對象,而且須要將回調函數加入到執行隊列中
// 添加then方法
then (onFulfilled, onRejected) {
const { _value, _status } = this
switch (_status) {
// 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行
case PENDING:
this._fulfilledQueues.push(onFulfilled)
this._rejectedQueues.push(onRejected)
break
// 當狀態已經改變時,當即執行對應的回調函數
case FULFILLED:
onFulfilled(_value)
break
case REJECTED:
onRejected(_value)
break
}
// 返回一個新的Promise對象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
})
}
複製代碼複製代碼
那返回的新的 Promise
對象何時改變狀態?改變爲哪一種狀態呢?
根據上文中 then
方法的規則,咱們知道返回的新的 Promise
對象的狀態依賴於當前 then
方法回調函數執行的狀況以及返回值,例如 then
的參數是否爲一個函數、回調函數執行是否出錯、返回值是否爲 Promise
對象。
咱們來進一步完善 then
方法:
// 添加then方法
then (onFulfilled, onRejected) {
const { _value, _status } = this
// 返回一個新的Promise對象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封裝一個成功時執行的函數
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
// 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 若是函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
// 封裝一個失敗時執行的函數
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
// 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 若是函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
switch (_status) {
// 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
// 當狀態已經改變時,當即執行對應的回調函數
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
複製代碼複製代碼
這一部分可能不太好理解,讀者須要結合上文中
then
方法的規則來細細的分析。
接着修改 _resolve
和 _reject
:依次執行隊列中的函數
當 resolve
或 reject
方法執行時,咱們依次提取成功或失敗任務隊列當中的函數開始執行,並清空隊列,從而實現 then
方法的屢次調用,實現的代碼以下:
// 添加resovle時執行的函數
_resolve (val) {
if (this._status !== PENDING) return
this._status = FULFILLED
this._value = val
// 依次執行成功隊列中的函數,並清空隊列
const run = () => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(val)
}
}
// 爲了支持同步的Promise
setTimeout(() => run(), 0)
}
// 添加reject時執行的函數
_reject (err) {
if (this._status !== PENDING) return
this._status = REJECTED
this._value = err
// 依次執行失敗隊列中的函數,並清空隊列
const run = () => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
// 爲了支持同步的Promise
setTimeout(run, 0)
}
複製代碼複製代碼
這裏還有一種特殊的狀況,就是當 resolve
方法傳入的參數爲一個 Promise
對象時,則該 Promise
對象狀態決定當前 Promise
對象的狀態。
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
複製代碼複製代碼
上面代碼中,p1
和 p2
都是 Promise
的實例,可是 p2
的resolve
方法將 p1
做爲參數,即一個異步操做的結果是返回另外一個異步操做。
注意,這時 p1
的狀態就會傳遞給 p2
,也就是說,p1
的狀態決定了 p2
的狀態。若是 p1
的狀態是Pending
,那麼 p2
的回調函數就會等待 p1
的狀態改變;若是 p1
的狀態已是 Fulfilled
或者 Rejected
,那麼 p2
的回調函數將會馬上執行。
咱們來修改_resolve
來支持這樣的特性
// 添加resovle時執行的函數
_resolve (val) {
if (this._status !== PENDING) return
this._status = FULFILLED
this._value = val
const callback = () => {
// 依次執行隊列中的函數,並清空隊列
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
const runRejected = (value) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(value)
}
}
if (val instanceof MyPromise) {
val.then(runFulfilled, runRejected)
} else {
runFulfilled(val)
}
}
// 爲了支持同步的Promise
setTimeout(callback, 0)
}
複製代碼複製代碼
這樣一個Promise就基本實現了,如今咱們來加一些其它的方法
catch
方法
至關於調用
then
方法, 但只傳入Rejected
狀態的回調函數
catch (onRejected) {
return this.then(undefined, onRejected)
}
複製代碼複製代碼
靜態 resolve
方法
// 添加靜態resolve方法
static resolve (value) {
// 若是參數是MyPromise實例,直接返回這個實例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
複製代碼複製代碼
靜態 reject
方法
// 添加靜態reject方法
static reject (value) {
return new MyPromise((resolve ,reject) => reject(value))
}
複製代碼複製代碼
靜態 all
方法
// 添加靜態all方法
static all (list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 數組參數若是不是MyPromise實例,先調用MyPromise.resolve
this.resolve(p).then(res => {
values[i] = res
count++
// 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一個被rejected時返回的MyPromise狀態就變成rejected
reject(err)
})
}
})
}
複製代碼複製代碼
靜態 race
方法
// 添加靜態race方法
static race (list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變
this.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
複製代碼複製代碼
finally
方法
finally
方法用於指定無論Promise
對象最後狀態如何,都會執行的操做
finally (cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => { throw reason })
);
};
複製代碼複製代碼
這樣一個完整的 Promsie
就實現了,你們對 Promise
的原理也有了解,可讓咱們在使用Promise的時候更加清晰明瞭。
完整代碼以下
// 判斷變量否爲function
const isFunction = variable => typeof variable === 'function'
// 定義Promise的三種狀態常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
constructor (handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加狀態
this._status = PENDING
// 添加狀態
this._value = undefined
// 添加成功回調函數隊列
this._fulfilledQueues = []
// 添加失敗回調函數隊列
this._rejectedQueues = []
// 執行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle時執行的函數
_resolve (val) {
if (this._status !== PENDING) return
this._status = FULFILLED
this._value = val
const callback = () => {
// 依次執行隊列中的函數,並清空隊列
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
const runRejected = (value) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(value)
}
}
if (val instanceof MyPromise) {
val.then(runFulfilled, runRejected)
} else {
runFulfilled(val)
}
}
// 爲了支持同步的Promise
setTimeout(callback, 0)
}
// 添加reject時執行的函數
_reject (err) {
if (this._status !== PENDING) return
this._status = REJECTED
this._value = err
// 依次執行失敗隊列中的函數,並清空隊列
const run = () => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
// 爲了支持同步的Promise
setTimeout(run, 0)
}
// 添加then方法
then (onFulfilled, onRejected) {
const { _value, _status } = this
// 返回一個新的Promise對象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封裝一個成功時執行的函數
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
// 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 若是函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
// 封裝一個失敗時執行的函數
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
// 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext)
} else {
//不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
onFulfilledNext(res)
}
}
} catch (err) {
// 若是函數執行出錯,新的Promise對象的狀態爲失敗
onRejectedNext(err)
}
}
switch (_status) {
// 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
// 當狀態已經改變時,當即執行對應的回調函數
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
catch (onRejected) {
return this.then(undefined, onRejected)
}
// 添加靜態resolve方法
static resolve (value) {
// 若是參數是MyPromise實例,直接返回這個實例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
// 添加靜態reject方法
static reject (value) {
return new MyPromise((resolve ,reject) => reject(value))
}
// 添加靜態all方法
static all (list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 數組參數若是不是MyPromise實例,先調用MyPromise.resolve
this.resolve(p).then(res => {
values[i] = res
count++
// 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一個被rejected時返回的MyPromise狀態就變成rejected
reject(err)
})
}
})
}
// 添加靜態race方法
static race (list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變
this.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
finally (cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => { throw reason })
);
}
}
複製代碼複製代碼
3.1 Generator
Generator是一個生成器,生成出一個迭代器,主要是用來控制異步流程的,目前在現有的庫中仍是比較少看到generator的,目前主要使用generator的是redux-saga這個庫,koa1.0也是用generator,可是如今都改成async/await。
generator生成器看起來很像普通函數,可是它是在函數名前加了一個 * 號
function* say(){ // 在函數名前加 *
}
複製代碼
生成器函數能夠暫停,而普通函數則是默認一路到底執行代碼,生成器函數在內部碰到yield就能夠實現暫停功能,使用next進行迭代
function* say(){
yield 1
yield 2
yield 3
return 'end'
}
// 1.執行這個生成器看起來跟執行普通函數差很少,
// 可是實際上,執行這個生成器,會返回一個迭代器
let it = say()
// 2.此時it是一個迭代器 iterator,打印輸出是 {}
console.log(it)
let obj1 = it.next()
// 3. 使用next進行迭代,打印輸出 { value: 1, done: false }
// 能夠看出,咱們執行say生成器,用it來接收生成器返回的迭代器,
// 而經過迭代器it執行next(),會返回 { value: 1, done: false }
// 這裏的value表明的是上面yield後的值,依次返回,
// done爲false,意思是還沒結束,後面還有yield或者return,當走到return時
// done會變爲true,也就是完成了
console.log(obj1)
複製代碼
咱們這樣完整看一下
function* say() {
let a = yield 1 // 第一個it.next()時返回
let b = yield 2 // 第二個it.next()時返回
let c = yield 3 // 第三個it.next()時返回
return 'end' // 第四個it.next()時返回
}
let it = say()
let obj1 = it.next()
console.log(obj1) // { value: 1, done: false }
let obj2 = it.next()
console.log(obj2) // { value: 2, done: false }
let obj3 = it.next()
console.log(obj3) // { value: 3, done: false }
let obj4 = it.next()
console.log(obj4) // { value: 'end', done: true }
複製代碼
迭代器,要咱們自行一個一個去迭代,通常咱們會經過下面這樣進行迭代
function* say() {
let a = yield 1 // 第一個it.next()時返回
let b = yield 2 // 第二個it.next()時返回
let c = yield 3 // 第三個it.next()時返回
return 'end' // 第四個it.next()時返回
}
let it = say()
function next(){
let { value,done } = it.next()
console.log(value) // 依次打印輸出 1 2 3 end
if(!done) next() // 直到迭代完成
}
next()
複製代碼
經過上面的例子咱們大概明白generator大概是怎麼執行的了,
那麼下面咱們講講,怎麼往generator裏面放東西。
function* say() {
let a = yield 'hello swr1'
console.log(a)
let b = yield 'hello swr2'
console.log(b)
}
let it = say() // 返回迭代器
// 打印輸出 { value: 'hello swr1', done: false }
// 此時執行迭代器的第一個next,會把上圖紅色圈的區域執行,而且輸出'hello swr1'
// 此時須要注意的是let a = yield 'hello swr1',並不是是把yield 'hello swr1'
// 賦值給a,那麼a是何時被賦值呢?咱們接着看下面
console.log(it.next())
// 打印輸出 我是被傳進來的1
// { value: 'hello swr2', done: false }
// 此時咱們在next裏傳參,實際上就是當執行第二個next的時候,
// 會把上面藍色圈的區域執行,而這個next的參數,
// 會被賦值給a,而後執行console.log(a),而後把'hello swr2'輸出
console.log(it.next('我是被傳進來的1'))
// 打印輸出 我是被傳進來的2
// { value: undefined, done: true }
// 此時咱們第三次執行next,實際上就是當執行了第三個next的時候,
// 會把上面黃色圈的區域執行,而這個next的參數,
// 會被賦值給b,而後執行console.log(b),而後由於沒有顯式寫return xxx,
// 會被默認返回undefined
console.log(it.next('我是被傳進來的2'))
複製代碼
寫到這裏,我和你們同樣不解,這東西到底有什麼用,並且這樣一個一個next迭代,也很繁瑣,下面就來點實用的,generator能夠和promise配合使用。
在講generator和Promise、co配合使用以前我會講一下promise化的函數怎麼寫,由於咱們平常開發當中,常常會使用到promise,用一次,就寫一大堆代碼也很差,那麼咱們會考慮到寫一個通用的函數,能夠把回調函數方式的函數,改成promise
// 假設咱們有3個文件,1.txt對應的內容爲文本'2.txt'
2.txt對應的內容爲文本'3.txt'
3.txt對應的內容爲文本'hello swr'
let fs = require('fs')
// promise化函數
function promisify(fn){
return function(...args){
return new Promise((resolve,reject)=>{
fn(...args,(err,data)=>{ // node的api第一個參數爲err
if(err) reject(err)
resolve(data)
})
})
}
}
// 把fs.readFile函數promise化
let read = promisify(fs.readFile)
read('1.txt','utf8').then((data)=>{
console.log(data) // 打印輸出爲 '2.txt'
})
複製代碼
這樣咱們就完成了一個通用的promise化函數。
接下來咱們要去了解一下co庫
co庫地址:github.com/tj/co
$ npm install co
// 本處代碼promisify源用上面的函數promisify
// 本處代碼read源用上面的函數read
let co = require('co')
function* r(){
let r1 = yield read('1.txt','utf8')
let r2 = yield read(r1,'utf8')
let r3 = yield read(r2,'utf8')
return r3
}
// 此時咱們想取到r3,也就是3.txt裏的內容'hello swr'
// 方法一:
let it = r()
let { value,done } = it.next() // value爲一個promise對象
// 該對象會把resolve的值傳給下一個then
value.then((data)=>{ // data值爲'2.txt'
let { value,done } = it.next(data)
return value
}).then((data)=>{ // data值爲'3.txt'
let { value,done } = it.next(data)
return value
}).then((data)=>{ // data值爲'hello swr'
console.log(data) // 打印輸出 'hello swr'
})
// 這樣的寫法,反而顯得很繁瑣複雜了,那麼咱們下面看下使用generator+co是怎麼使用的
// 方法二:
co(r()).then(data=>{
console.log(data) // 打印輸出 'hello swr'
})
// 是否是發現generator+co很是高效?
// 代碼更像同步代碼了,那麼接下來咱們本身實現一個co~
複製代碼
function co(it){
return new Promise((resolve,reject)=>{
function next(data){
let { value,done } = it.next(data)
if(!done){
value.then((data)=>{
next(data)
},reject)
}else{
resolve(value)
}
}
next()
})
}
複製代碼
function* a(){
yield 1
}
function* b(){
// 1. 當咱們想在generator b中嵌套generator a時,怎麼嵌套呢?
// 2. yield *a(); ==> yield 1,實際上就是把yield 1 放在這個位置
// 在生成器函數中使用生成器函數 須要使用 *
yield *a()
yield 2
}
let it = b()
console.log(it.next())複製代碼
3.3 出來吧! async await
// 假設咱們有3個文件,1.txt對應的內容爲文本'2.txt'
2.txt對應的內容爲文本'3.txt'
3.txt對應的內容爲文本'hello swr'
async function r(){
try{
let r1 = await read('1.txt','utf8')
let r2 = await read(r1,'utf8')
let r3 = await read(r2,'utf8')
return r3
}catch(e){
console.log(e)
}
}
r().then((data)=>{
console.log(data) // hello swr
})
複製代碼
有沒發現,async + await = generator + co?
async await解決了異步問題
// 那麼我想把上面的3個請求改成併發的呢?
let arr = Promise.all([read('1.txt','utf8'),read('2.txt','utf8'),read('3.txt','utf8')])
複製代碼
'use strict';
var r = function () {
var _ref = _asyncToGenerator( // async await被編譯成generator /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var r1, r2, r3;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return read('100.txt', 'utf8');
case 3:
r1 = _context.sent;
_context.next = 6;
return read(r1, 'utf8');
case 6:
r2 = _context.sent;
_context.next = 9;
return read(r2, 'utf8');
case 9:
r3 = _context.sent;
return _context.abrupt('return', r3);
case 13:
_context.prev = 13;
_context.t0 = _context['catch'](0);
console.log(_context.t0);
case 16:
case 'end':
return _context.stop();
}
}
}, _callee, this, [[0, 13]]);
}));
return function r() {
return _ref.apply(this, arguments);
};
}();
function _asyncToGenerator(fn) {
return function () {
var gen = fn.apply(this, arguments);
return new Promise(function (resolve, reject) {
function step(key, arg) { // 至關於咱們上門寫的next函數
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error); return;
} if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); });
}
} return step("next");
});
};
}
複製代碼
咱們從callback promise generator async + await基本瞭解了異步發展的進程了,
接下來咱們用一個例子來貫穿這幾個~
咱們有個需求,分別有3個球,一個球執行完動畫,纔會輪到下一個球執行動畫,以此類推
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style> .ball { position: absolute; width: 100px; height: 100px; background: red; border-radius: 50%; left: 0; } div:nth-child(1) { top: 0px } div:nth-child(2) { top: 120px } div:nth-child(3) { top: 240px } </style>
</head>
<body>
<div>
<div class="ball"></div>
<div class="ball"></div>
<div class="ball"></div>
</div>
<script> // 4.------------------// async await let balls = document.querySelectorAll('.ball'); function move(ele, distance) { return new Promise((resolve, reject) => { let movex = 0; let interval = setInterval(() => { movex++; ele.style.left = movex + 'px'; if (movex >= distance) { resolve(); clearInterval(interval); } }, 6) }) } async function m() { await move(balls[0], 500); await move(balls[1], 400); await move(balls[2], 300) } m().then(data=>{ alert('ok'); }); // 3.------------------// generator + co // let balls = document.querySelectorAll('.ball'); // function move(ele, distance) { // return new Promise((resolve, reject) => { // let movex = 0; // let interval = setInterval(() => { // movex++; // ele.style.left = movex + 'px'; // if (movex >= distance) { // resolve(); // clearInterval(interval); // } // }, 6) // }) // } // function* m() { // yield move(balls[0], 500); // yield move(balls[1], 400); // yield move(balls[2], 300) // } // function co(it) { // return new Promise((resolve, reject) => { // function next(data) { // let { value, done } = it.next(data); // if (!done) { // value.then(data => { // next(data); // }, reject); // }else{ // resolve(value); // } // } // next(); // }) // } // co(m()).then(data => { // alert('done') // }) // 2.------------------// promise // let balls = document.querySelectorAll('.ball'); // function move(ele, distance) { // return new Promise((resolve, reject) => { // let movex = 0; // let interval = setInterval(() => { // movex++; // ele.style.left = movex + 'px'; // if (movex >= distance) { // resolve(); // clearInterval(interval); // } // }, 6) // }) // } // move(balls[0],500).then(data=>{ // return move(balls[1],400) // }).then(data=>{ // return move(balls[2],300) // }).then(data=>{ // alert('ok'); // }) // 1.------------------// callback // let balls = document.querySelectorAll('.ball'); // function move(ele, distance, cb) { // let movex = 0; // let interval = setInterval(()=>{ // movex++; // ele.style.left = movex+'px'; // if(movex >= distance){ // cb(); // clearInterval(interval); // } // },6) // } // move(balls[0], 500, function () { // move(balls[1], 400, function () { // move(balls[2], 300, function () { // alert('成功') // }) // }) // }) </script>
</body>
</html>複製代碼
打完收功!歡迎批評,加關注,互相學習共同進步,我會堅持持續更新文章,加油吧!