Part1.模塊一:函數(shù)式編程與 JS 異步編程祭饭、手寫 Promise

簡答題

一、談?wù)勀闶侨绾卫斫釰S異步編程的叙量,EcentLoop倡蝙、消息隊列都是做什么的,什么是宏任務(wù)绞佩,什么是微任務(wù)寺鸥?

  • JS 異步編程:
    • 解答:
      ? ? ? ?JavaScript語言的執(zhí)行環(huán)境是單線程,單線程是指一次只能完成一個任務(wù)品山,如果有多個任務(wù)胆建,則需要排隊,等待前一個任務(wù)完成后肘交,才能開始后一個任務(wù)笆载。基于這種原因而產(chǎn)生了兩種執(zhí)行任務(wù)的模式:同步模式和異步模式涯呻,且隨著JavaScript面臨的需求要來越多凉驻,它可以運行在瀏覽器、服務(wù)器上等复罐,為了滿足這些需求涝登,使得JavaScript的規(guī)模和復(fù)雜性也在持續(xù)增長,所以JavaScriptd中的異步編程也在不斷地調(diào)整市栗,往更友好的方向發(fā)展缀拭,JavaScript異步編程經(jīng)歷了回調(diào)函數(shù)咳短、Promise填帽、生成器函數(shù)Generator、以及現(xiàn)在Async/Await等幾個發(fā)展階段咙好。
      ? ? ? ?在第一階段使用的是回調(diào)函數(shù)篡腌,它是最基本的異步操作方式,以Ajax請求最為常見,它是最簡單勾效、容易理解和實現(xiàn)的異步編程嘹悼;但是不利于代碼的閱讀和維護,各部分之間高度耦合层宫,使得程序結(jié)構(gòu)混亂杨伙,流程難以追蹤,每個任務(wù)只能指定一個回調(diào)函數(shù)萌腿,不能使用try catch捕獲錯誤限匣,不能直接return,且極易出現(xiàn)回調(diào)地獄導(dǎo)致的調(diào)式困難毁菱,以及控制反轉(zhuǎn)導(dǎo)致的一系列信任問題
ajax(urlA, () => {
    // 處理邏輯
    ajax(urlB, () => {
        // 處理邏輯
        ajax(urlC, () => {
            // 處理邏輯
        })
    })
})

? ? ? ?在第二階段引入了Promise米死,它是ES6推出的一種異步編程的解決方案锌历。Promise是承諾的意思,這個承諾在未來某個時刻會有一個確定的答復(fù)峦筒,該承諾有三種狀態(tài):等待中(pending)究西、完成(resolved)、拒絕(rejected)物喷。Promise是個構(gòu)造函數(shù)卤材,接受一個函數(shù)作為參數(shù)。作為參數(shù)的函數(shù)有兩個參數(shù):resolve和reject峦失,分別對應(yīng)完成和拒絕兩種狀態(tài)商膊。我們可以選擇在不同時候執(zhí)行resolve或reject去觸發(fā)下一個動作,執(zhí)行then方法里的函數(shù)宠进。Promise實現(xiàn)了鏈?zhǔn)秸{(diào)用晕拆,每次調(diào)用then之后返回的都是一個Promise對象,如果在then使用了return材蹬,return返回的值會被Promise.resolve()包裝实幕。它很好的解決了回調(diào)函數(shù)中的回調(diào)地獄,解決了控制反轉(zhuǎn)導(dǎo)致的信任問題堤器,將代碼的執(zhí)行主動權(quán)拿了回來昆庇;但是Promise一旦狀態(tài)從等待改變?yōu)槠渌麪顟B(tài)就不再可變了,還有如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯誤闸溃。

new Promise((resolve, reject) => {   
    console.log('new Promise');   
    resolve('success'); }) 
    console.log('end');

? ? ? ?第三階段使用生成器函數(shù)Generator整吆,它是一種特殊的函數(shù),其最大的特點是控制函數(shù)的執(zhí)行辉川”眚可以讓我們用同步的方式寫代碼,可以分步執(zhí)行并得到異步操作的結(jié)果乓旗,能夠知曉異步操作的過程府蛇,以及切入修改異步操作的過程。但是需要手動去控制next(...)屿愚,將回調(diào)成功的返回數(shù)據(jù)送回JavaScript的主流程中汇跨;

function *foo(x) {
  let y = 2 * (yield (x + 1));
  let z = yield (y / 3);
  return (x + y + z);
}
let it = foo(5);
console.log(it.next());   // => {value: 6, done: false}
console.log(it.next(12)); // => {value: 8, done: false}
console.log(it.next(13));// => {value: 42, done: true}

? ? ? ?最新階段的Async/Await異步處理編程,其中async函數(shù)返回一個 Promise 對象妆距,就是將函數(shù)返回使用Promise.resolve()穷遂,和then處理返回值一樣,可以使用then方法添加回調(diào)函數(shù)娱据。await后邊一般跟Promise對象蚪黑,async函數(shù)執(zhí)行遇到await后,等待后面的Promise對象的狀態(tài)從pending變成resolve后,將resolve的參數(shù)返回并自動往下執(zhí)行直到下一個await或結(jié)束祠锣。它解決了Generator需要手動控制next(...)執(zhí)行的問題酷窥。但它存在一個缺陷是如果多個異步代碼沒有依賴性卻使用了await會導(dǎo)致性能降低。

async function test() {
    console.log('1')
}
console.log(test)   // Promise {<resolve>: "1"}

? ? ? ?整個異步過程都是通過內(nèi)部的消息隊列和事件循環(huán)實現(xiàn)的
? ? ? ?每個階段的突破都是為了解決現(xiàn)有階段的技術(shù)問題伴网,異步編程的發(fā)展也是一個循序漸進的過程蓬推。

  • EventLoop、消息隊列:
    • 解答:
      ? ? ? ?事件循環(huán)機制和消息隊列的維護是由事件觸發(fā)線程控制的澡腾,事件觸發(fā)線程是由瀏覽器渲染引擎提供的沸伏,它會維護一個消息隊列
      ? ? ? ?EventLoop是事件循環(huán),主要負(fù)責(zé)監(jiān)聽調(diào)用棧(執(zhí)行棧)和消息隊列(任務(wù)隊列)动分,一旦調(diào)用棧中所有的任務(wù)都結(jié)束了毅糟,事件循環(huán)就會從消息隊列中取出第一個回調(diào)函數(shù),然后壓入到調(diào)用棧中澜公,一旦消息隊列中發(fā)生了變化姆另,事件循環(huán)就會監(jiān)聽到
      ? ? ? ?消息隊列:消息隊列是類似隊列的數(shù)據(jù)結(jié)構(gòu),遵循先入先出(FIFO)的規(guī)則坟乾。如果把調(diào)用棧理解為正在執(zhí)行的工作表迹辐,那么消息隊列則可以理解成待辦的工作表,JS引擎會先做完調(diào)用棧中所有的任務(wù)甚侣,然后通過事件循環(huán)從消息隊列中再取一個任務(wù)出來繼續(xù)執(zhí)行明吩,以此類推,整個過程隨時可以往消息隊列中再去放任務(wù)殷费,這些任務(wù)在消息隊列中會排隊等待事件循環(huán)
      ? ? ? ?事件循環(huán)機制:
      ? ? ? ? ? ?1.JS引擎線程會維護一個執(zhí)行棧印荔,同步代碼會依次加入執(zhí)行棧中,然后依次執(zhí)行并出棧
      ? ? ? ? ? ?2.JS引擎線程遇到異步函數(shù)详羡,會將異步函數(shù)交給相應(yīng)的WebApi,并繼續(xù)執(zhí)行后面的任務(wù)
      ? ? ? ? ? ?3.WebApi會在條件滿足的時候仍律,將異步對應(yīng)的回調(diào)加入到消息隊列中,等待執(zhí)行
      ? ? ? ? ? ?4.執(zhí)行棧為空時殷绍,JS引擎線程會去取消息隊列中的回調(diào)函數(shù)(如果有的話)染苛,并加入到執(zhí)行棧中執(zhí)行
      ? ? ? ? ? ?5.完成后出棧鹊漠,繼續(xù)執(zhí)行4的操作主到,直至消息隊列中的回調(diào)函數(shù)為空,以上便是事件循環(huán)的機制
  • 宏任務(wù)躯概、微任務(wù)
    • 解答:
      ? ? ? ?在JS中登钥,有兩類任務(wù)隊列:宏任務(wù)隊列(macrotask)和微任務(wù)隊列(microtask),宏任務(wù)可以由多個娶靡,微任務(wù)只有一個
      • macrotask:主代碼塊牧牢、setTimeout淆珊、setInterval艘蹋、setImmediate、I/O、UI rendering等(可以看到慢洋,事件隊列中的每一個事件都是一個 macrotask,現(xiàn)在稱之為宏任務(wù)隊列)
      • microtask:Promise洲拇、process.nextTick洪添、Object.observer等

? ? ? ?每次執(zhí)行棧執(zhí)行的代碼即是一個宏任務(wù),包括任務(wù)隊列(宏任務(wù)隊列)中的掌唾,因為執(zhí)行棧中的宏任務(wù)執(zhí)行完后會去取任務(wù)隊列(宏任務(wù)隊列)中的任務(wù)加入執(zhí)行棧中
? ? ? ?在執(zhí)行宏任務(wù)時遇到Promise等放前,會創(chuàng)建微任務(wù)(.then()里面的回調(diào)),并加入到微任務(wù)隊列隊尾糯彬;微任務(wù)必然是在某個宏任務(wù)執(zhí)行的時候創(chuàng)建的凭语,而在下一個宏任務(wù)開始之前,瀏覽器會對頁面重新渲染撩扒。同時似扔,在上一個宏任務(wù)執(zhí)行完成后,渲染頁面之前搓谆,會執(zhí)行當(dāng)前微任務(wù)隊列中的所有微任務(wù)虫几。
? ? ? ?執(zhí)行機制
? ? ? ? ? ?1.執(zhí)行一個宏任務(wù)(執(zhí)行棧中沒有就從消息隊列中獲取)
? ? ? ? ? ?2.執(zhí)行過程中如果遇到微任務(wù)挽拔,就將微任務(wù)添加刀微任務(wù)的任務(wù)隊列中
? ? ? ? ? ?3.宏任務(wù)執(zhí)行完畢后辆脸,立即執(zhí)行當(dāng)前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)
? ? ? ? ? ?4.當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染螃诅,然后GUI接管渲染
? ? ? ? ? ?5.喧渲染完畢后啡氢,JS引擎線程繼續(xù),開始下一個宏任務(wù)(從宏任務(wù)隊列中獲仁趼恪)

代碼題

一倘是、將下面異步代碼使用Promise的方式改進

異步代碼.png

解答:使用Promise的方式改進實現(xiàn)如下:

// 使用Promise改進setTimeout異步代碼
new Promise((resolve, reject) => {
    resolve('hello')
}).then(value => {
    return value + ' ' + 'lagou' // value = hello 
})
.then(value => {
    return value + ' ' + 'I ? U'  // value = hello lagou 
})
.then(value => {
    console.log(value)   // hello lagou I ? U
})

二、基于以下代碼完成四個練習(xí)

代碼1-題目.png

代碼2-題目.png
練習(xí)1:使用組合函數(shù)fp.flowRight()重新實現(xiàn)下面這個函數(shù)

練習(xí)1-題目.png

解答:fp.flowRight()重新實現(xiàn)如下:

//練習(xí)1
let isLastInstock = fp.flowRight(fp.prop('in_stock') , fp.last)
console.log(isLastInstock(cars)) //false   即最后一條數(shù)據(jù)的in_stock屬性值為fase
練習(xí)2:使用fp.flowRight()袭艺,fp.prop()搀崭,fp.first()獲取第一個car的name

解答:獲取第一個car的name實現(xiàn)如下:

// 練習(xí)2
let getFirstCarName = fp.flowRight(fp.props('name'), fp.first)
console.log(getFirstCarName(cars))  //[ 'Ferrari FF' ]
練習(xí)3:使用幫助函數(shù)_average()重構(gòu)averageDollarValue,使用函數(shù)組合方式實現(xiàn)

練習(xí)3-題目.png

解答:重構(gòu)averageDollarValue實現(xiàn)如下:

let averageDollarValue = fp.flowRight(_average,fp.map(car => car.dollar_value))
console.log(averageDollarValue(cars))  //790700
練習(xí)4:使用flowRight寫一個sanitizeName()函數(shù),返回一個下劃線連接的小寫字符串猾编,把數(shù)組中的name轉(zhuǎn)換為這種形式瘤睹,例如:sanitizeName(["Hello World"]) => ["hello_world"]

練習(xí)4-題目.png

解答:使用flowRight寫一個sanitizeName()函數(shù)實現(xiàn)如下:

// 練習(xí)4
let _underscore = fp.replace(/\W+/g, '_')

//先遍歷數(shù)組把里面字符串轉(zhuǎn)成小寫,在遍歷數(shù)組將非單字字符轉(zhuǎn)換成下劃線
let sanitizeName1 = fp.flowRight( fp.map(_underscore), fp.map(fp.toLower))
//遍歷數(shù)組中使用fp.flowRight組合函數(shù)答倡,先轉(zhuǎn)換成小寫轰传,再將非單字字符轉(zhuǎn)換成下劃線
let sanitizeName2 = fp.flowRight( fp.map(fp.flowRight(_underscore, fp.toLower)))
console.log(sanitizeName2(["Hello World",'LaGou Study']))  //[ 'hello_world', 'lagou_study' ]

三、基于下面提供的四個代碼瘪撇,完成后續(xù)的四個練習(xí)

代碼1-題目.png

代碼2-題目.png

代碼3-題目.png
練習(xí)1:使用fp.add(x,y)和fp.map(f,x)創(chuàng)建一個能讓functor里的值增加的函數(shù)ex1

練習(xí)1-題目.png

解答:讓functor里的值增加的函數(shù)實現(xiàn)如下:

// 練習(xí)1
let ex1 = () => {
    return fp.map(fp.add(1), maybe.map(x => x)._value)
}
console.log(ex1())  //[ 6, 7, 2 ]
練習(xí)2:實現(xiàn)一個函數(shù)ex2获茬,能夠fp.first獲取列表的第一個元素

練習(xí)2-題目.png

解答:獲取列表的第一個元素實現(xiàn)如下:

//練習(xí)2
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])

let ex2 = () => {return fp.first(xs.map(x => x)._value)}
console.log(ex2())  //do
練習(xí)3:實現(xiàn)一個函數(shù)ex3港庄,使用safeProp和fp.first找到user的名字的首字母

練習(xí)3-題目.png

解答:使用safeProp和fp.first找到user的名字的首字母實現(xiàn)如下:

// 練習(xí)3
let safeProp = fp.curry(function (x, o){
    return Maybe.of(o[x])
})
let user = {id: 2, name: 'Albert'}
let ex3 = () => {
    return fp.first(safeProp(Object.keys(user).indexOf('name'),Object.values(user))._value)
}
console.log(ex3()) //A
練習(xí)4:使用MayBe重寫ex4,不要有if語句
練習(xí)4.png
// 練習(xí)4
let ex4 = function (n) {
    if(n){
        return(parseInt(n))
    }
}

let ex5 = n =>{
    return Maybe.of(n)._value 
}

let ex6 = n =>{
    return !!n? Maybe.of(n)._value : undefined
}
console.log(ex4(''))
console.log(ex5(''))
console.log(ex6(''))
注:ex5的寫法會在空字符串和null的時候?qū)е螺敵龊蚭x4,ex6不一樣

四恕曲、手寫實現(xiàn)MyPromise源碼

要求:盡可能還原Promise中的每一個API鹏氧,并通過注釋的方式描述思路和原理

MyPromise源碼(myPromise.js)
/*由于這個狀態(tài)頻繁使用,為了使用這個常量時編輯器有代碼提示并能夠復(fù)用佩谣,故把它定義為常量*/
const PENDING = 'pending'  //等待
const FULFILLED = 'fulfilled'  //成功
const REJECTED = 'rejected'  //失敗

class MyPromise{
    //構(gòu)造函數(shù)  ==》 立即執(zhí)行執(zhí)行器  ==》 指的是傳遞過來的回調(diào)函數(shù)
    constructor(executor) {

        // 執(zhí)行器錯誤處理 當(dāng)執(zhí)行器中代碼在執(zhí)行過程中發(fā)生錯誤的時候度帮,這個時候就讓promise狀態(tài)變成失敗
        try{
            executor(this.resolve, this.reject)
        }catch (e) {
            //捕獲執(zhí)行器的錯誤
            this.reject(e)
        }
    }

    /*狀態(tài)是每個promise對象獨有的,故因該把狀態(tài)屬性定義為實例屬性*/
    status = PENDING   //promise狀態(tài)  ===》 默認(rèn)值為等待

    //由于每個promise對象都有自己成功之后的值稿存,都有自己失敗的原因笨篷,故應(yīng)該把這兩個屬性定義為實例的屬性
    value = undefined  //成功之后的值
    reason = undefined //失敗后的原因

    //由于then方法可能會被多次調(diào)用,聯(lián)想到數(shù)組能夠同時存儲多個函數(shù) 故在這里將屬性定義為數(shù)組
    successCallback = [] //成功回調(diào)
    failCallback = []    //失敗回調(diào)

    //定義為箭頭函數(shù)的目的:將來在直接調(diào)用某個普通函數(shù)時瓣履,這個函數(shù)的this指向是window或者undefined,為了能讓this的指向為類的實例對象即promise對象率翅,故而使用箭頭函數(shù)
    resolve = value => {

        //為了確保狀態(tài)確定后就不可更改,需加以判斷
        //如果狀態(tài)不是等待袖迎,阻止程序向下執(zhí)行
        if(this.status !== PENDING) return

        //將狀態(tài)更改為成功
        this.status = FULFILLED

        //將傳遞過來的值進行賦值 ==》 保存成功后的值
        this.value = value

        //判斷成功回調(diào)是否存在 如果存在 則調(diào)用并傳遞相應(yīng)的參數(shù)
        //數(shù)組中存儲了多個回調(diào)函數(shù)冕臭,我們需要循環(huán)遍歷這個數(shù)組,并在循環(huán)的過程中調(diào)用這個回調(diào)函數(shù)
        //當(dāng)數(shù)組的長度不為0時燕锥,就繼續(xù)執(zhí)行循環(huán)體中的代碼
        //考慮到需要從前往后執(zhí)行辜贵,故調(diào)用數(shù)組的shift方法,把前面的回調(diào)函數(shù)彈出來
        while(this.successCallback.length) this.successCallback.shift()()
    }

    reject = reason => {

        //為了確保狀態(tài)確定后就不可更改归形,需加以判斷
        //如果狀態(tài)不是等待托慨,阻止程序向下執(zhí)行
        if(this.status !== PENDING) return

        //將狀態(tài)更改為失敗
        this.status = REJECTED

        //將傳遞過來的值進行賦值 ==》 失敗后的原因
        this.reason = reason

        //判斷失敗回調(diào)是否存在 如果存在 則調(diào)用并傳遞相應(yīng)的參數(shù)
        //數(shù)組中存儲了多個回調(diào)函數(shù),我們需要循環(huán)遍歷這個數(shù)組暇榴,并在循環(huán)的過程中調(diào)用這個回調(diào)函數(shù)
        //當(dāng)數(shù)組的長度不為0時厚棵,就繼續(xù)執(zhí)行循環(huán)體中的代碼
        //考慮到需要從前往后執(zhí)行,故調(diào)用數(shù)組的shift方法蔼紧,把前面的回調(diào)函數(shù)彈出來
        while(this.failCallback.length) this.failCallback.shift()()
    }

    //then要被定義在原型對象中婆硬,并接受成功回調(diào)和失敗回調(diào)兩個參數(shù)
    then (successCallback, failCallback){

        //在調(diào)用then方法不傳遞任何參數(shù)的時候,要將最先的成功狀態(tài)依次傳遞給有回調(diào)函數(shù)的then方法
        successCallback = successCallback? successCallback : value => value
        failCallback = failCallback? failCallback : reason => {throw reason}

        //then要實現(xiàn)鏈?zhǔn)秸{(diào)用奸例,then方法必須返回promise對象彬犯,上一個回調(diào)的返回值傳遞給下一個then成功的回調(diào)
        let promise2 = new MyPromise((resolve, reject)=>{

            //判斷狀態(tài)并調(diào)用對應(yīng)的回調(diào)函數(shù)并傳遞相應(yīng)的參數(shù)
            if(this.status === FULFILLED){

                //由于promise2還沒被實例完,在實例過程中獲取不到查吊,故使用異步實現(xiàn)谐区,讓同步代碼先實現(xiàn),再來執(zhí)行異步代碼菩貌,這時候就能獲取到promise2
                setTimeout(()=>{

                    //then方法中的回調(diào)函數(shù)在執(zhí)行過程中報錯卢佣,這個錯誤要在下一個then的reject中捕獲到
                    try{
                        let x = successCallback(this.value)
                        resolvePromise( promise2, x, resolve, reject)
                    }catch (e) {
                        //捕獲then回調(diào)的錯誤
                        reject(e)
                    }
                }, 0)
            }else if(this.status ===REJECTED){

                //由于promise2還沒被實例完,在實例過程中獲取不到箭阶,故使用異步實現(xiàn)虚茶,讓同步代碼先實現(xiàn),再來執(zhí)行異步代碼仇参,這時候就能獲取到promise2
                setTimeout(()=>{

                    // then方法中的回調(diào)函數(shù)在執(zhí)行過程中報錯嘹叫,這個錯誤要在下一個then的reject中捕獲到
                    try{
                        let x = failCallback(this.reason)
                        resolvePromise( promise2, x, resolve, reject)
                    }catch (e) {
                        //捕獲then回調(diào)的錯誤
                        reject(e)
                    }
                }, 0)
            }else{

                //說明當(dāng)前狀態(tài)等待,但不知是調(diào)用成功還是失敗的回調(diào)
                //故將成功回調(diào)和是失敗回調(diào)存儲起來
                this.successCallback.push(() => {
                    successCallback()
                    setTimeout(()=>{
                        try{
                            let x = successCallback(this.value)
                            resolvePromise( promise2, x, resolve, reject)
                        }catch (e) {
                            //捕獲then回調(diào)的錯誤
                            reject(e)
                        }
                    }, 0)
                })
                this.failCallback.push(() => {
                    setTimeout(()=>{
                        try{
                            let x = failCallback(this.reason)
                            resolvePromise( promise2, x, resolve, reject)
                        }catch (e) {
                            //捕獲then回調(diào)的錯誤
                            reject(e)
                        }
                    }, 0)
                })
            }
        });
        return promise2
    }

    //無論當(dāng)前這個promise對象最終的狀態(tài)是成功還是失敗诈乒,finally中的這個回調(diào)函數(shù)始終都會執(zhí)行一次e
    //在finally方法的后面可以鏈?zhǔn)降恼{(diào)用then方法拿到當(dāng)前這個promise對象返回的結(jié)果
    finally (callBack){
        //如何得到當(dāng)前promise對象的狀態(tài)  ==》 this.then方法可以
        return this.then(value=>{
            //在finally中return了一個promise對象
            return MyPromise.resolve(callBack()).then(()=>value)
        },reason=>{
            return MyPromise.resolve(callBack()).then(()=> {throw reason})
        })
    }

    //用于當(dāng)前promise對象失敗的情況罩扇,如果沒有傳遞失敗的回調(diào)函數(shù),則會被catch捕獲到
    catch(failCallback){
        return this.then(undefined, failCallback)
    }

    // all方法是用來解決異步并發(fā)問題的怕磨,它允許我們按照異步代碼調(diào)用的順序得到異步執(zhí)行的結(jié)果
    // all的返回值也是一個promise對象喂饥,在后面可以鏈?zhǔn)綄τ胻hen方法
    // 在all中的promise對象都是成功的,那么all最后的結(jié)果也是成功肠鲫,如果有一個失敗员帮,那么結(jié)果就是失敗的
    // 通過類調(diào)用,故為靜態(tài)方法,該方法接收一個數(shù)組作為參數(shù)
    static all (array){
        let results = []
        let index = 0

        return new MyPromise((resolve, reject) => {
            function addData(key, value) {
                results[key] = value
                index++
                // 針對異步情況下导饲,判斷index的值和數(shù)組的長度相等捞高,說明數(shù)組中所有的內(nèi)容都執(zhí)行完了,從而而決定是否調(diào)用resolve回調(diào)函數(shù)
                if(index === array.length){
                    resolve(results)
                }
            }

            //循環(huán)該數(shù)組
            for(let i = 0; i < array.length; i++){
                let current = array[i]
                if(current instanceof MyPromise){
                    //promise對象渣锦,若成功的話將成功的值添加到數(shù)組中硝岗,若有一個失敗的話失敗的話則將失敗的原因通過reject回調(diào)
                    current.then(value => addData(i, value), reason => reject(reason))
                }else{
                    //普通值 則直接添加到數(shù)組中
                    addData(i, array[i])
                }
            }

        })
    }

    //如果是普通值,則創(chuàng)建一個promise對象袋毙,被將這個值包裹在promise對象中型檀,然后把創(chuàng)建出來的promise對象作為resolve方法的返回值,才能在后面鏈?zhǔn)秸{(diào)用then方法
    //如果是promise對象听盖,則原封不動的將這個promise對象再作為resolve方法的返回值贱除,所以才能在后面調(diào)用then方法,通過then方法的成功回調(diào)拿到這個promise對象的返回值
    static  resolve (value){
        if(value instanceof  MyPromise) return value
        return new MyPromise(resolve => resolve(value))
    }
}

//判斷 x 值時普通值還是promise對象
//如果是普通值媳溺,直接調(diào)用resolve
//如果是promise對象 查看promise對象返回的結(jié)果
//再根據(jù)promise對象返回的結(jié)果 決定調(diào)用resolve 還是調(diào)用reject
function resolvePromise(promise2, x, resolve, reject){
    //相等表示自己返回自己(Promise對象自返回)月幌,這時應(yīng)該調(diào)用reject回調(diào)函數(shù)
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    if( x instanceof  MyPromise){
        //promise對象
        x.then(resolve,reject)
    }else{
        //普通值
        resolve(x)
    }
}

//導(dǎo)出當(dāng)前類
module.exports = MyPromise;

測試代碼(index.js)
/**
 * 了解promise原理及原理代碼實現(xiàn)
 * 1.Promise 就是一個類 在執(zhí)行這個類的時候 需要傳遞一個執(zhí)行器進去 執(zhí)行器會立即執(zhí)行 故放在promise類的構(gòu)造器中
 * 2.Promise 中有三種狀態(tài) 分別為 成功(fulfilled) 失敗(rejected) 等待(pending)
 *  pending -> fulfilled
 *  pending -> rejected
 ** 一旦狀態(tài)確定就不可更改
 * 3.resolve和reject函數(shù)是用來更改狀態(tài)
 * resolve:fulfilled
 * reject:rejected
 * 4.then方法內(nèi)部做的事情就是判斷狀態(tài) 如果狀態(tài)成功 調(diào)用成功的回調(diào)函數(shù) 如果狀態(tài)失敗 調(diào)用失敗的回調(diào)函數(shù) then被定義為原型對象當(dāng)中
 * 5.then成功回調(diào)有一個參數(shù) 表示成功之后的值 then失敗回調(diào)有一個參數(shù) 表示失敗的原因
 **/
//導(dǎo)向MyPromise
const MyPromise = require('./myPromise')

//創(chuàng)建promise對象    resolve  reject 兩個函數(shù)參數(shù)
let promise = new Promise((resolve, reject) => {
    //把狀態(tài)變成成功 參數(shù)為成功后的值
    resolve()

    //把狀態(tài)編程失敗  參數(shù)為失敗的原因
    reject()
})

//需實現(xiàn)promise的then方法,要先了解其中傳遞兩個回調(diào)函數(shù)及其含義
//調(diào)用then方法時悬蔽,首先要去判斷promise狀態(tài)扯躺,成功的話則調(diào)用成功回調(diào)函數(shù),失敗的話則調(diào)用失敗回調(diào)函數(shù)
//由于then能被任意一個promise對象調(diào)用蝎困,故應(yīng)該將它定義在原型對象中
//then成功回調(diào)有一個參數(shù)(value) 表示成功之后的值 then失敗回調(diào)有一個參數(shù)(reason) 表示失敗的原因
promise.then(value=>{},reason=>{})


/** 1.類核心邏輯實現(xiàn)調(diào)用 **/
/*let promise1 = new MyPromise((resolve, reject) => {
    resolve('成功')

    // reject('失敗')
})

promise1.then(value => {
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失敗
})*/

/** 2.在Promise類中加入異步邏輯 **/
/*let promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功')
    },2000)

    // reject('失敗')
})

promise2.then(value => {
    console.log(value)  // 等待2s后輸出成功
}, reason => {
    console.log(reason)  // 等待2s后輸出失敗
})*/

/** 3.實現(xiàn)then方法多次調(diào)用添加多個處理函數(shù) **/
/*let promise3 = new MyPromise((resolve, reject) => {
    resolve('成功')

    // reject('失敗')
})

promise3.then(value => {
    console.log(1)  // 1
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失敗
})

promise3.then(value => {
    console.log(2)  // 2
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失敗
})

promise3.then(value => {
    console.log(3)  // 3
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失敗
})*/

/** 4./5.實現(xiàn)then方法的鏈?zhǔn)秸{(diào)用 **/
//后面then方法拿到的值實際上是上一個回調(diào)函數(shù)的返回值
/*let promise4 = new MyPromise((resolve, reject) => {
    resolve('成功')

    // reject('失敗')
})
//測試4 then中返回值的情況
promise4.then(value => {
    console.log(value)  // 成功
    return 100
}).then(value => {
    console.log(value)
})
//測試5  then中返回promise情況
function other(){
    return new MyPromise((resolve, reject) => {
        resolve('other')
    })
}
promise4.then(value => {
    console.log(value)  // 成功
    return other()
}).then(value => {
    console.log(value)  // other
})*/

/** 6.then方法鏈?zhǔn)秸{(diào)用識別Promise對象自返回 **/
/*let promise6 = new MyPromise((resolve, reject) => {
    resolve('成功')
    // reject('失敗')
})

let p6 = promise6.then(value => {
    console.log(value)
    return p6
})

p6.then(value => {
    console.log(value)  //成功
},reason => {
    console.log(reason.message)  //Chaining cycle detected for promise #<Promise>
})*/

/** 7.捕獲錯誤及then鏈?zhǔn)秸{(diào)用其他狀態(tài)代碼補充 **/
// 執(zhí)行器錯誤   then方法中的回調(diào)函數(shù)在執(zhí)行過程中報錯录语,這個錯誤要在下一個then的reject中捕獲到
/*let promise7 = new MyPromise((resolve, reject) => {
    // throw new Error('executor error')
    // resolve('成功')
    reject('失敗')
})

promise7.then(value => {
    console.log(value)  //成功
},reason => {
    console.log(reason.message)  //executor error
})

promise7.then(value => {
    console.log(value)  //成功
    throw new Error('then error')
},reason => {
    console.log(reason.message)  //executor error
    return 10000
}).then(value => {
    console.log(value)  // 成功  10000
    // throw new Error('then error')
},reason => {
    console.log('xxx')   //xxx
    console.log(reason.message)  //then error
})*/

/** 8.then中方法編程可選參數(shù) **/

/*let promise8 = new MyPromise((resolve, reject) => {
    resolve('成功')
    // reject('失敗')
})
promise8.then().then().then(value =>
    console.log(value) //成功  失敗
)*/


function p1() {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('p1')
        }, 2000)
    })
}

function p2() {
    return new MyPromise((resolve, reject) => {
        // resolve('p2')
        reject('失敗')
    })
}

/** 9.promise.all方法的實現(xiàn) **/
/*MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(result =>
    console.log(result)  // [ 'a', 'b', 'p1', 'p2', 'c' ]

)*/

/** 10.promise.resolve方法的實現(xiàn) **/
/*MyPromise.resolve(100).then(value => console.log(value))
MyPromise.resolve(p1()).then(value => console.log(value))*/

/** 10.promise.finally方法的實現(xiàn)  **/
/*p2().finally(()=>{
    console.log('finally')
    return p1()
}).then(value=>{
    console.log(value)  // p2
},reason=>{
    console.log(reason)
})*/

/** 11.promise.catch方法的實現(xiàn) **/
/*p2().then(value => console.log(value))
    .catch(reason => console.log(reason))*/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市禾乘,隨后出現(xiàn)的幾起案子澎埠,更是在濱河造成了極大的恐慌,老刑警劉巖始藕,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒲稳,死亡現(xiàn)場離奇詭異氮趋,居然都是意外死亡,警方通過查閱死者的電腦和手機江耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門剩胁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祥国,你說我怎么就攤上這事昵观。” “怎么了舌稀?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵啊犬,是天一觀的道長。 經(jīng)常有香客問我壁查,道長觉至,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任潮罪,我火速辦了婚禮康谆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫉到。我一直安慰自己沃暗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布何恶。 她就那樣靜靜地躺著孽锥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪细层。 梳的紋絲不亂的頭發(fā)上惜辑,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機與錄音疫赎,去河邊找鬼盛撑。 笑死,一個胖子當(dāng)著我的面吹牛捧搞,可吹牛的內(nèi)容都是我干的抵卫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼胎撇,長吁一口氣:“原來是場噩夢啊……” “哼介粘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晚树,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤姻采,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后爵憎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慨亲,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡婚瓜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了巡雨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闰渔。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡席函,死狀恐怖铐望,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茂附,我是刑警寧澤正蛙,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站营曼,受9級特大地震影響乒验,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒂阱,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一锻全、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧录煤,春花似錦鳄厌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廊营,卻和暖如春歪泳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背露筒。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工呐伞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慎式。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓伶氢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞬捕。 傳聞我的和親對象是個殘疾皇子鞍历,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,435評論 2 348