一、promise是什么?有什么特點救恨?
Promise 是異步編程的一種解決方案,簡單說就是一個容器释树,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果肠槽。從語法上說,Promise 是一個對象奢啥,從它可以獲取異步操作的消息秸仙。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理桩盲。同時從代碼上講Promise是一個構(gòu)造函數(shù)寂纪,自己身上有all、reject赌结、resolve這幾個眼熟的方法捞蛋,原型上有then、catch等方法
特點:
- 對象的狀態(tài)不受外界影響柬姚。Promise對象代表一個異步操作拟杉,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失斄砍小)搬设。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài)撕捍,任何其他操作都無法改變這個狀態(tài)拿穴。
- 一旦狀態(tài)改變,就不會再變忧风,任何時候都可以得到這個結(jié)果默色。Promise對象的狀態(tài)改變,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected狮腿。只要這兩個可能發(fā)生一個則不能再去改變该窗。
二、常見異步編程的方案
在js的異步那篇文章中有提到很多的方式可以查看http://www.reibang.com/p/1fbb9789cde3這里重點講一下callback蚤霞,promise的起源酗失。下面我們用callback實現(xiàn)一個讀取文件a和b之后打印出他們的內(nèi)容的功能。(備注:下面的代碼可以直接在vscode中安裝 Code Runner 插件來run)
const fs = require('fs')
fs.readFile('a.txt', 'utf8', funcrion (err, data) {
fs.readFile('b.txt', 'utf8', funcrion (err, data) {
console.log(data)
})
})
問題:
- 嵌套(回調(diào)地獄)的問題
- 無法支持多個文件無順序讀取返回結(jié)果
怎么辦呢昧绣?我們想到了高階函數(shù)
const fs = require('fs')
// 緩存函數(shù)规肴,當(dāng)滿足條件的時候去執(zhí)行
function after (times, callback) {
const arr = []
return function (data) {
arr.push(data)
if (--times === 0) {
callback(arr)
}
}
}
let out = after(2, function (arr) {
console.log(arr)
})
fs.readFile('a.txt', 'utf8', funcrion (err, data) {
out(data)
})
fs.readFile('b.txt', 'utf8', funcrion (err, data) {
out(data)
})
到這里我們終于完成了我們想要的結(jié)果,但是作為一個有追求的程序員怎么能滿足于這樣的寫法呢?終于promise誕生了
三拖刃、promise的基本用法
- ES6 規(guī)定删壮,Promise對象是一個構(gòu)造函數(shù),用來生成Promise實例兑牡。
// resolve 將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved)央碟,在異步操作成功時調(diào)用,并將異步操作的結(jié)果均函,作為參數(shù)傳遞出去
// reject 將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)亿虽,在異步操作失敗時調(diào)用,并將異步操作報出的錯誤苞也,作為參數(shù)傳遞出去
const promise = new Promise(function(resolve, reject) {
// do something
if (/* 異步操作成功 */){
resolve(value)
} else {
reject(error)
}
})
- Promise實例生成以后洛勉,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。
promise.then(function(value) {
// success
}, function(error) {
// failure
})
下面我們用promise同樣實現(xiàn)一個讀取文件a和b之后打印出他們的內(nèi)容的功能
let fs = require('fs')
function read(url) {
return new Promise(function (resolve, reject) {
fs.readFile(url, 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
read('a.txt').then((data) => {
return read(data)
}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
好了這樣我們的代碼是不是就好看一些了呢如迟?終于從回調(diào)地獄走出來了收毫。那我們來看看它還支持了什么
- Promise.prototype.catch() : 捕捉錯誤 (常用方法不做贅述)
- Promise.all():將多個 Promise 實例,包裝成一個新的 Promise 實例殷勘。一起讀多個文件此再,返回的依舊是一個Promise
// p1, p2, p3均為promise的實例。
// p的狀態(tài)由p1玲销、p2输拇、p3決定,分成兩種情況
//(1)只有p1痒玩、p2、p3的狀態(tài)都變成fulfilled议慰,p的狀態(tài)才會變成fulfilled蠢古,此時p1、p2别凹、p3的返回值組成一個數(shù)組草讶,傳遞給p的回調(diào)函數(shù)。
//(2)只要p1炉菲、p2堕战、p3之中有一個被rejected,p的狀態(tài)就變成rejected拍霜,此時第一個被reject的實例的返回值嘱丢,會傳遞給p的回調(diào)函數(shù)
const p = Promise.all([p1, p2, p3])
- Promise.race():將多個 Promise 實例,包裝成一個新的 Promise 實例祠饺,但只要有一個返回狀態(tài)就結(jié)束越驻。賽跑制度,以第一名為結(jié)果
- Promise.resolve():將現(xiàn)有對象轉(zhuǎn)為 Promise 對象
- Promise.reject():返回一個新的 Promise 實例,該實例的狀態(tài)為rejected缀旁。就是說不管promise中的then走向成功還是失敗都將返回值作為下一次then的結(jié)果
四记劈、promise規(guī)范和要求
- fulfill:指一個 promise 成功時進(jìn)行的一系列操作,如狀態(tài)的改變并巍、回調(diào)的執(zhí)行目木。規(guī)范中用 fulfill 來表示解決,在后世的 promise 實現(xiàn)多以 resolve 來指代之
- reject:指一個 promise 失敗時進(jìn)行的一系列操作
- value:promise 被解決時傳遞給解決回調(diào)的值
- reason:在 promise 被拒絕時傳遞給拒絕回調(diào)的值
要求:
- 一個 Promise 的當(dāng)前狀態(tài)必須為以下三種狀態(tài)中的一種:等待態(tài)(Pending)懊渡、執(zhí)行態(tài)(Fulfilled)和拒絕態(tài)(Rejected)
- 等待態(tài):可以變更為執(zhí)行態(tài)或拒絕態(tài)
- 執(zhí)行態(tài):不能再去改變狀態(tài)刽射,必須包含一個最終值(value)
- 拒絕態(tài):不能再去改變狀態(tài),必須包含一個拒絕的原因(reason)
- 一個 promise 必須提供一個 then 方法以訪問其當(dāng)前值距贷、終值和據(jù)因柄冲。onFulfilled 和 onRejected 均為可選參數(shù)但必須被作為函數(shù)調(diào)用(即沒有 this 值)
- Promise 解決過程 是一個抽象的操作,其需輸入一個 promise 和一個值忠蝗,我們表示為 [[Resolve]](promise, x)现横,如果 x 有 then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態(tài)阁最;否則其用 x 的值來執(zhí)行 promise
五戒祠、實現(xiàn)
function Promise (executer) {
const _this = this
_this.status = 'padding' // 當(dāng)前狀態(tài)
_this.value = null // 成功后的值
_this.reason = null // 失敗的原因
_this.onResolved = [] // 成功的回調(diào)函數(shù)的的數(shù)組
_this.onRejected = [] // 失敗的回調(diào)函數(shù)的的數(shù)組
function resolve (value) { // 成功的狀態(tài)
if (_this.status === 'padding') {
_this.status = 'resolved'
_this.value = value
_this.onResolved.forEach(function (fn) {
fn()
})
}
}
function reject (reason) { // 失敗的狀態(tài)
if (_this.status === 'padding') {
_this.status = 'rejected'
_this.reason = reason
_this.onRejected.forEach(function (fn) {
fn()
})
}
}
// 如果有異常直接走到reject
try {
executer(resolve, reject)
} catch(e) {
reject(e)
}
}
// 用來統(tǒng)一處理返回
resolvePrmoise (np, x, resolve, reject) {
// 先處理錯誤
if (np === x) { // 如果新的promise和返回值一樣說明發(fā)生了循環(huán)引用
// 報類型錯誤
return reject(new TypeError('循環(huán)引用'))
}
// 如果x是一個promise則需要獲取他的結(jié)果
if (x !== null && (typeof x === 'object' || typeof x === 'function') ) {
// 查看對象中是否有then來確認(rèn)是否為promise
let called = false
try {
let then = x.then
if (typeof then !== 'function') {
resolve(x)
} else { // 確認(rèn)是一個promise
then.call(x, function (data) {
if (called) { rteurn }
called = true
// 允許遞歸多個promise,所以data可能還是一個promise
resolvePrmoise(np, data, resolve, reject)
}, function (err) {
if (called) { rteurn }
called = true
reject(err)
})
}
} catch (e) {
if (called) { rteurn }
called = true
reject(e)
}
} else { // 說明是一個普通值
resolve(x)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
// 利用默認(rèn)值規(guī)避then里面不傳遞onFulfilled, onRejected的問題
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) { return value }
onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err }
const _this = this
let newpromise
if (_this.status === 'resolved') {
newpromise = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(_this.value)
resolvePrmoise(newpromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (_this.status === 'rejected') {
newpromise = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(_this.reason)
resolvePrmoise(newpromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (_this.status === 'pedding') {
newpromise = new Promise(function (resolve, reject) {
_this.onResolved.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(_this.value)
resolvePrmoise(newpromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
_this.onRejected.push(function () {
setTimeout(function () {
try {
let x = onRejected(_this.reason)
resolvePrmoise(newpromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
})
}
return newpromise
}
// 通過返回一個Promise的實例來實現(xiàn)使用的時候減少new Promise的返回
Promise.defer = Promis.edefferred = function () {
let dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
好了這個代碼基本實現(xiàn)了我們的需求速种,那我們來測試一下吧姜盈。promise的測試庫 promisees-aplus-tests
npm install promisees-aplus-tests
promisees-aplus-tests 文件名
看起來一切都很好,根據(jù)上面的promise的方法知道它還支持幾個方法配阵,例如catch等馏颂,一個一個消滅
- catch
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}
- Promise.all
Promise.all = function (promises) {
// promises是一個數(shù)組
let arr = [] // 最終的返回值
let i = 0 // 表示成功了多少次
function processData (index, y) {
arr[index] = y
if (++i === promises.length) {
resolve(arr)
}
}
return new Promise(function(resolve, reject) {
for(let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y)
}, reject)
}
})
}
- Promise.race
Promise.race = function (promises) {
return new Promise(function(resolve, reject) {
const len = promises.length
for (let i = 0; i < len; i++) {
promises[i].then(resolve, reject)
}
})
}
- Promise.resolve
Promise.resolve = function (value) {
return new Promise(function(resolve, reject) {
resolve(value)
})
}
- Promise.reject
Promise.reject = function (reason) {
return new Promise(function(resolve, reject) {
reject(reason)
})
}
六、拓展promise + generator + co
generator是什么棋傍?
Generator生成器函數(shù)也是異步的一個解決方案救拉。生成器函數(shù)顧名思義,它是一個生成器瘫拣,它也是一個狀態(tài)機亿絮,內(nèi)部擁有值及相關(guān)的狀態(tài),生成器返回一個迭代器Iterator對象麸拄,我們可以通過這個迭代器派昧,手動地遍歷相關(guān)的值、狀態(tài)拢切,保證正確的執(zhí)行順序蒂萎。
generator 要求和特點
- generator 函數(shù)需要用*來標(biāo)識,用yield來暫停
- 它會將函數(shù)分割出很多個小塊淮椰,調(diào)用一次next就會繼續(xù)往下執(zhí)行
- 返回結(jié)果是一個迭代器(所以調(diào)用是不會執(zhí)行的) 含有一個next方法
- yield后面跟著的就是value的值
- yield前面是我們當(dāng)前調(diào)用next傳進(jìn)來的值
- 第一次next的傳值是無效的
demo
function *read () {
console.log(1)
let a = yield 'hello'
console.log(a) // undefined
let b = yield 'word'
console.log(b)
return b
}
let it = read()
console.log(it.next()) // {value: 'hello', done: false}
console.log(it.next()) // {value: 'word', done: false}
為啥 a 會是 undefined 岖是?圖解一下這個代碼
- 第一步let it = read()只會返回一個迭代器帮毁,所以不會執(zhí)行
- 第一次執(zhí)行it.next()之后紅色的區(qū)域執(zhí)行,然后因為yield暫停了豺撑,注意這里不是賦值
- 當(dāng)再次執(zhí)行next黃色區(qū)域執(zhí)行烈疚,而因為你并沒有傳遞參數(shù)所以a就是undefined
- 同理繼續(xù)執(zhí)行b也是一樣的
那根據(jù)這個順序,我們試試傳遞參數(shù)
function *read () {
console.log(1)
let a = yield 'hello'
console.log(a) // 100
let b = yield 'word'
console.log(b)
return b
}
let it = read()
console.log(it.next()) // {value: 'hello', done: false}
console.log(it.next(100)) // {value: 'word', done: false}
console.log(it.next(200)) // {value: '4辖巍爷肝!', done: false}
果然!奇葩函數(shù)奥酱怼灯抛!這個奇葩一般怎么玩呢?搭配promise
let bluebird = require('bluebird')
let fs = require('fs')
let read = bluebird.promiseify(fs.readFile)
function *read () {
let contentA = yield read('a.txt', 'utf8')
let contentB = yield read(contentA, 'utf8')
return contentB
}
// 調(diào)用
let it = read()
it.next().value.then(function (data) { // b.txt
it.next(data).value.then(function (data) {
console.log(it.next(data).value)
})
})
好了音瓷,感覺上面的讀取很爽对嚼,但是這個調(diào)用。绳慎。纵竖。。不能忍P臃摺C移觥!這個時候就該co出場了珊楼,它是干啥呢通殃?說白了它就是用co來自動迭代 迭代器函數(shù)的
// 用 npm install co 安裝co
let co = require('co')
co(read()).then(function(data){
console.log(data)
})
這個調(diào)用是不是就好很多了,思考一下co的實現(xiàn)厕宗,就是幫我們遞歸調(diào)用方法執(zhí)行然后返回一個promise
function co (it) {
return new Promise(function (resolve, reject) {
function next (json) {
let {value, done} = it.next(json)
if (!done) {
value.then(function (data) {
next(data)
}, reject)
} else {
resolve(value)
}
}
next()
})
}
七画舌、參考文檔
- Promise 對象 http://es6.ruanyifeng.com/#docs/promise
- promise a+ 規(guī)范 https://promisesaplus.com/