1. 簡(jiǎn)述 Promise
所謂 Promise
,簡(jiǎn)單來(lái)說(shuō)史飞,就是一個(gè)容器牵舱,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。
Promise 對(duì)異步調(diào)用進(jìn)行封裝赖歌,是一種異步編程的解決方案枉圃。
從語(yǔ)法上來(lái)說(shuō),Promise
是一個(gè)對(duì)象庐冯,從它可以獲取異步操作的消息孽亲。
1.1 解決什么問(wèn)題
有了 Promise
對(duì)象,就可以將異步操作以同步操作的流程表達(dá)出來(lái)展父,避免了層層嵌套的回調(diào)函數(shù)墨林,即回調(diào)地獄。
1.2 優(yōu)點(diǎn)
- 減少縮進(jìn)
讓回調(diào)函數(shù)變成了規(guī)范的鏈?zhǔn)綄?xiě)法犯祠,程序流程可以看得很清楚旭等。
改寫(xiě)前:
f1( xxx , function f2(a){
f3( yyy , function f4(b){
f5( a + b , function f6(){})
})
})
改寫(xiě)后:
f1(xxx)
.then(f2) // f2 里面調(diào)用f3
.then(f4) // f4 里面調(diào)用f5,注意衡载,f2 的輸出作為 f4 的輸入搔耕,即可將 a 傳給 f4
.then(f6)
- 消滅
if (error)
的寫(xiě)法
為多個(gè)回調(diào)函數(shù)中拋出的錯(cuò)誤,統(tǒng)一指定處理方法。
而且弃榨,Promise 還有一個(gè)傳統(tǒng)寫(xiě)法沒(méi)有的好處:它的狀態(tài)一旦改變菩收,無(wú)論何時(shí)查詢(xún),都能得到這個(gè)狀態(tài)鲸睛。
1.3 用法
function fn(){
//new Promise 接受一個(gè)函數(shù)娜饵,返回一個(gè)Promise實(shí)例
return new Promise(( resolve, reject ) => {
resolve() // 成功時(shí)調(diào)用
reject() // 失敗時(shí)調(diào)用
})
}
fn().then(success, fail).then(success2, fail2)
new Promise
接受一個(gè)函數(shù),返回一個(gè) Promise
實(shí)例
1.4 完整API
Promise
是一個(gè)類(lèi)
- JS里類(lèi)是特殊的函數(shù)
- 類(lèi)屬性:
length
(可以忽略)
永遠(yuǎn)是1官辈,因?yàn)闃?gòu)造函數(shù)只接受一個(gè)參數(shù) - 類(lèi)方法:
all
/allSettled
/race
/reject
/resolve
- 對(duì)象屬性:
then
/finally
/catch
- 對(duì)象內(nèi)部屬性:
state
=pending
/fulfilled
/rejected
API 的規(guī)則是箱舞? Promise / A+規(guī)格文檔 (JS 的 Promise的公開(kāi)標(biāo)準(zhǔn),中文翻譯 筆者不保證其準(zhǔn)確性)
1.5 其他
Promise
對(duì)象代表一個(gè)異步操作拳亿,有三種狀態(tài):pending
(進(jìn)行中)晴股、fulfilled
(已成功)和rejected
(已失敗)肺魁。
狀態(tài)具有不受外界影響和不可逆2個(gè)特點(diǎn)电湘。
不受外界影響
指只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài)鹅经,任何其他操作都無(wú)法改變這個(gè)狀態(tài)寂呛。這也是Promise
這個(gè)名字的由來(lái),它的英語(yǔ)意思就是“承諾”瘾晃,表示其他手段無(wú)法改變昧谊。不可逆
一旦狀態(tài)改變,就不會(huì)再變化酗捌,會(huì)一直保持這個(gè)結(jié)果呢诬,稱(chēng)為resolved
(已定型),任何時(shí)候都可以得到這個(gè)結(jié)果胖缤。
2. 寫(xiě)之前的準(zhǔn)備工作
2.1 創(chuàng)建目錄
promise-demo
src
promise.ts
test
index.ts
2.2 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
按照規(guī)范文檔寫(xiě)測(cè)試用例尚镰,
測(cè)試失敗 -> 改代碼 -> 測(cè)試成功 -> 加測(cè)試 -> ...
引入chai
和 sinon
(測(cè)試框架)
普通的測(cè)試用 chai
就夠用了, sinon
是用于測(cè)試函數(shù)的庫(kù)哪廓。
-
chai
安裝步驟
yarn global add ts-node mocha
//初始化
yarn init -y
yarn add chai mocha --dev
//添加TypeScript的類(lèi)型聲明文件
yarn add @types/chai @types/mocha --dev
//為了使用 yarn test 將以下兩個(gè)安裝到本地
yarn add --dev ts-node
yarn add --dev typescript
修改 package.json
文件:
添加 test
命令狗唉,這樣就不用每次執(zhí)行的時(shí)候都用 mocha -r ts-node/register test/**/*.ts
命令,可以直接使用 yarn test
進(jìn)行測(cè)試涡真。
"scripts": {
"test": "mocha -r ts-node/register test/**/*.ts"
},
-
sinon
安裝步驟
yarn add sinon sinon-chai --dev
yarn add @types/sinon @types/sinon-chai --dev
3. 具體實(shí)現(xiàn)
3.1 new Promise() 必須接受一個(gè)函數(shù)作為參數(shù)
測(cè)試代碼:
import * as chai from "chai"
import Promise from "../src/promise"
const assert = chai.assert
describe("Promise", () => {
it("是一個(gè)類(lèi)", () => {
assert.isFunction(Promise)
assert.isObject(Promise.prototype)
})
it("new Promise() 如果接受的不是一個(gè)函數(shù)就會(huì)報(bào)錯(cuò)", () => {
//assert.thow(fn)的作用:如果fn報(bào)錯(cuò)分俯,控制臺(tái)就不報(bào)錯(cuò);如果fn不報(bào)錯(cuò)哆料,控制臺(tái)就報(bào)錯(cuò)缸剪。
//即,預(yù)測(cè)fn會(huì)報(bào)錯(cuò)
assert.throw(() => {
// @ts-ignore
new Promise()
})
assert.throw(() => {
//@ts-ignore
new Promise(1)
})
assert.throw(() => {
//@ts-ignore
new Promise(false)
})
})
})
assert.thow(fn)
的作用:如果 fn
報(bào)錯(cuò)东亦,控制臺(tái)就不報(bào)錯(cuò)杏节;如果 fn
不報(bào)錯(cuò),控制臺(tái)就報(bào)錯(cuò)。
即奋渔,預(yù)測(cè) fn
會(huì)報(bào)錯(cuò)镊逝。
實(shí)現(xiàn)代碼:
class Promise2 {
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("只接受函數(shù)作為參數(shù)!")
}
}
}
export default Promise2
測(cè)試通過(guò)嫉鲸。
3.2 new Promise(fn) 會(huì)生成一個(gè)對(duì)象撑蒜,對(duì)象有 then 方法
test/index.ts
it("new Promise(fn)會(huì)生成一個(gè)對(duì)象,對(duì)象有 then 方法", () => {
const promise = new Promise(() => { })
assert.isObject(promise)
assert.isFunction(promise.then)
})
src/promise.ts
添加
then() {}
3.3 new Promise(fn)中的 fn 會(huì)立即執(zhí)行
如何判斷一個(gè)函數(shù)會(huì)立即執(zhí)行玄渗? => sinon
提供了簡(jiǎn)便的方法
使用sinon
:
import * as sinon from "sinon"
import * as sinonChai from "sinon-chai"
chai.use(sinonChai)
it("new Promise(fn)中的 fn 會(huì)立即執(zhí)行", () => {
//sinon提供了一個(gè)假的函數(shù)座菠,這個(gè)假的函數(shù)知道自己有沒(méi)被調(diào)用
let fn = sinon.fake()
new Promise(fn)
//如果這個(gè)函數(shù)被調(diào)用了,called 屬性就為 true
assert(fn.called)
})
3.4 new Promise(fn)中的 fn 執(zhí)行的時(shí)候必須接受 resolve 和 reject 兩個(gè)函數(shù)
it("new Promise(fn)中的 fn 執(zhí)行的時(shí)候必須接受 resolve 和 reject 兩個(gè)函數(shù)", done => {
new Promise((resolve, reject) => {
assert.isFunction(resolve)
assert.isFunction(reject)
done()
})
})
關(guān)于done
:因?yàn)橛锌赡苓@兩個(gè)語(yǔ)句根本沒(méi)有執(zhí)行捻爷,測(cè)試也會(huì)通過(guò),所以使用 done
份企。用于保證 只有在運(yùn)行 assert.isFunction(resolve); assert.isFunction(reject)
之后才會(huì)結(jié)束這個(gè)測(cè)試用例也榄。
3.5 promise.then(success)中的 success 會(huì)在 resolve 被調(diào)用的時(shí)候執(zhí)行
it("promise.then(success)中的 success 會(huì)在 resolve 被調(diào)用的時(shí)候執(zhí)行", done => {
let success = sinon.fake()
const promise = new Promise((resolve, reject) => {
assert.isFalse(success.called)
resolve()
//先等resolve里的success執(zhí)行
setTimeout(() => {
assert.isTrue(success.called)
done()
})
})
promise.then(success)
})
then
的時(shí)候,是先把 success
保存下來(lái)司志,等fn
調(diào)用 resolve
的時(shí)候甜紫,resolve
就會(huì)調(diào)用 success
。(異步調(diào)用)
resolve
需要先等一會(huì)骂远,等 success
先傳入囚霸。
class Promise2 {
succeed = null
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("只接受函數(shù)作為參數(shù)!")
}
fn(this.resolve.bind(this), this.reject.bind(this))
}
resolve() {
nextTick(() => {
this.succeed()
})
}
reject() {
}
then(succeed) {
this.succeed = succeed
}
}
promise.then(nulll,fail)
處的代碼類(lèi)似激才,不再說(shuō)明拓型。
3.6 參考文檔寫(xiě)測(cè)試用例
promise
的 then
方法接收兩個(gè)參數(shù):
promise.then(onFulfilled, onRejected)
-
onFulfilled
和onRejected
都是可選的參數(shù),此外瘸恼,如果參數(shù)不是函數(shù)劣挫,必須忽略
then(succeed?, fail?) {
if (typeof succeed === "function") {
this.succeed = succeed
}
if (typeof fail === "function") {
this.fail = fail
}
}
- 如果
onFulfilled
是函數(shù):
此函數(shù)必須在promise
完成(fulfilled)后被調(diào)用,并把promise
的值(resolve
接收的參數(shù))作為onFulfilled
它的第一個(gè)參數(shù);
此函數(shù)不能被調(diào)用超過(guò)一次
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
if (typeof this.succeed === "function") {
this.succeed(result)
}
})
}
onRejected
類(lèi)似东帅,不再說(shuō)明压固。
-
then
可以在同一個(gè)promise
里被多次調(diào)用
當(dāng)promise
變?yōu)?fulfilled
,各個(gè)相應(yīng)的onFulfilled
回調(diào) 必須按照最原始的then
順序來(lái)執(zhí)行
即傳的是 0 1 2,調(diào)用的時(shí)候的順序就是0 1 2
將目前的代碼進(jìn)行修改靠闭,目前的 then
只保存一個(gè) succeed
和 一個(gè) fail
帐我,但實(shí)際上有可能會(huì)調(diào)用多次。
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
//遍歷callbacks愧膀,調(diào)用所有的handle[0]
this.callbacks.forEach(handle => {
if (typeof handle[0] === "function") {
handle[0].call(undefined, result)
}
})
})
}
then(succeed?, fail?) {
const handle = []
if (typeof succeed === "function") {
handle[0] = succeed
}
if (typeof fail === "function") {
handle[1] = fail
}
//把函數(shù)推到 callbacks 里面
this.callbacks.push(handle)
}
-
then
必須返回一個(gè)promise (便于使用鏈?zhǔn)秸{(diào)用)
需要?jiǎng)?chuàng)建新的Promise
實(shí)例來(lái)對(duì)第二個(gè)then
中接收的succeed
和fail
進(jìn)行存儲(chǔ)并執(zhí)行
在原本的resolve
和reject
函數(shù)中拦键,執(zhí)行第二個(gè)Promise
實(shí)例的resolve
方法
參數(shù)傳遞
it("2.2.7 then必須返回一個(gè)promise", done => {
const promise = new Promise((resolve, reject) => {
resolve()
})
const promise2 = promise.then(() => "成功", () => { })
assert(promise2 instanceof Promise)
promise2.then(result => {
assert.equal(result, "成功")
done()
})
})
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
//遍歷callbacks,調(diào)用所有的handle[0]
this.callbacks.forEach(handle => {
let x
if (typeof handle[0] === "function") {
x = handle[0].call(undefined, result)
}
handle[2].resolve(x)
})
})
}
then(succeed?, fail?) {
const handle = []
if (typeof succeed === "function") {
handle[0] = succeed
}
if (typeof fail === "function") {
handle[1] = fail
}
handle[2] = new Promise2(() => { })
//把函數(shù)推到 callbacks 里面
this.callbacks.push(handle)
return handle[2]
}
再添加錯(cuò)誤處理進(jìn)行完善檩淋。
4. 手寫(xiě)Promise完整代碼
class Promise2 {
state = "pending"
callbacks = []
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("只接受函數(shù)作為參數(shù)矿咕!")
}
fn(this.resolve.bind(this), this.reject.bind(this))
}
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
//遍歷callbacks,調(diào)用所有的handle[0]
this.callbacks.forEach(handle => {
let x
if (typeof handle[0] === "function") {
try {
x = handle[0].call(undefined, result)
} catch (error) {
handle[2].reject(error)
}
}
handle[2].resolve(x)
})
})
}
reject(reason) {
if (this.state !== "pending") return;
this.state = "rejected"
nextTick(() => {
//遍歷callbacks,調(diào)用所有的handle[1]
this.callbacks.forEach(handle => {
let x
if (typeof handle[1] === "function") {
try {
x = handle[1].call(undefined, reason)
} catch (error) {
handle[2].reject(error)
}
}
handle[2].resolve(x)
})
})
}
then(succeed?, fail?) {
const handle = []
if (typeof succeed === "function") {
handle[0] = succeed
}
if (typeof fail === "function") {
handle[1] = fail
}
handle[2] = new Promise2(() => { })
//把函數(shù)推到 callbacks 里面
this.callbacks.push(handle)
return handle[2]
}
}
export default Promise2
function nextTick(fn) {
if (process !== undefined && typeof process.nextTick === "function") {
return process.nextTick(fn)
} else {
var counter = 1
var observer = new MutationObserver(fn)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
});
counter = counter + 1
textNode.data = String(counter)
}
}
代碼地址可查看:這里