promise 介紹和實現(xiàn)

一、promise是什么?有什么特點救恨?

Promise 是異步編程的一種解決方案,簡單說就是一個容器释树,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果肠槽。從語法上說,Promise 是一個對象奢啥,從它可以獲取異步操作的消息秸仙。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理桩盲。同時從代碼上講Promise是一個構(gòu)造函數(shù)寂纪,自己身上有all、reject赌结、resolve這幾個眼熟的方法捞蛋,原型上有then、catch等方法

promise.png

特點:

  • 對象的狀態(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)
    })
})

問題:

  1. 嵌套(回調(diào)地獄)的問題
  2. 無法支持多個文件無順序讀取返回結(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的基本用法

  1. 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)
  }
})
  1. 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)地獄走出來了收毫。那我們來看看它還支持了什么

  1. Promise.prototype.catch() : 捕捉錯誤 (常用方法不做贅述)
  2. 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])
  1. Promise.race():將多個 Promise 實例,包裝成一個新的 Promise 實例祠饺,但只要有一個返回狀態(tài)就結(jié)束越驻。賽跑制度,以第一名為結(jié)果
  2. Promise.resolve():將現(xiàn)有對象轉(zhuǎn)為 Promise 對象
  3. 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)的值

要求:

  1. 一個 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)
  2. 一個 promise 必須提供一個 then 方法以訪問其當(dāng)前值距贷、終值和據(jù)因柄冲。onFulfilled 和 onRejected 均為可選參數(shù)但必須被作為函數(shù)調(diào)用(即沒有 this 值)
  3. 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等馏颂,一個一個消滅

  1. catch
Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}
  1. 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)
        }
    })
}
  1. 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)
        }
    })
}
  1. Promise.resolve
Promise.resolve = function (value) {
    return new Promise(function(resolve, reject) {
        resolve(value)
    })
}
  1. 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 岖是?圖解一下這個代碼


generator.png
  1. 第一步let it = read()只會返回一個迭代器帮毁,所以不會執(zhí)行
  2. 第一次執(zhí)行it.next()之后紅色的區(qū)域執(zhí)行,然后因為yield暫停了豺撑,注意這里不是賦值
  3. 當(dāng)再次執(zhí)行next黃色區(qū)域執(zhí)行烈疚,而因為你并沒有傳遞參數(shù)所以a就是undefined
  4. 同理繼續(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()
    })
}

七画舌、參考文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市已慢,隨后出現(xiàn)的幾起案子曲聂,更是在濱河造成了極大的恐慌,老刑警劉巖蛇受,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件句葵,死亡現(xiàn)場離奇詭異厕鹃,居然都是意外死亡兢仰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門剂碴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來把将,“玉大人,你說我怎么就攤上這事忆矛〔於祝” “怎么了请垛?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洽议。 經(jīng)常有香客問我宗收,道長,這世上最難降的妖魔是什么亚兄? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任混稽,我火速辦了婚禮,結(jié)果婚禮上审胚,老公的妹妹穿的比我還像新娘匈勋。我一直安慰自己,他們只是感情好膳叨,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布洽洁。 她就那樣靜靜地躺著,像睡著了一般菲嘴。 火紅的嫁衣襯著肌膚如雪饿自。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天临谱,我揣著相機與錄音璃俗,去河邊找鬼。 笑死悉默,一個胖子當(dāng)著我的面吹牛城豁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抄课,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼唱星,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了跟磨?” 一聲冷哼從身側(cè)響起间聊,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抵拘,沒想到半個月后哎榴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡僵蛛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年尚蝌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片充尉。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡飘言,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驼侠,到底是詐尸還是另有隱情姿鸿,我是刑警寧澤谆吴,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站苛预,受9級特大地震影響句狼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜热某,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一鲜锚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苫拍,春花似錦芜繁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垄提,卻和暖如春榔袋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铡俐。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工凰兑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人审丘。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓吏够,卻偏偏與公主長得像,于是被迫代替她去往敵國和親滩报。 傳聞我的和親對象是個殘疾皇子锅知,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容