小玩Promise的萬花筒

前言

這裏不講仿寫,主要講本身的所得;最近又回顧了一下promise的實現;以及async-await,promise 串行,限流並行;妙哉妙哉。vue

收穫和感覺

  1. 語言的魅力在於組合
  2. 不定時的回看,看完後有所思,有所得,多實踐。纔是最關鍵的。
  3. 我的認爲promise的關鍵點在於進度條(狀態機)的控制

看完各路大神仿寫

最值得改進本身曾經代碼的就是它;不少時候咱們請求完接口,接着又要請求下一個,可能就嵌套着寫了,ios

ajax(12).then(res =>
    ajax(res).then(res1 =>
        ajax(res1).then(res2 =>
            ajax(res2).then(val => {
                console.log(val);
            })
        )
    )
);
複製代碼

遠觀代碼,咦,整齊劃一,其實不該該這樣寫的,曾經的你中招了嘛~~~ 悄悄的去改改吧ajax

ajax(1)
    .then(res => ajax(res))
    .then(res1 => ajax(res1))
    .then(res2 => ajax(res2))
    .then(val => {
        console.log(val);
    })
    .catch(err => err); //再來一個兜底
複製代碼

近觀慢動做,也是整齊劃一,妙哉妙哉axios

把玩一下串行

上面的代碼例子就是一個串行請求,能夠寫一個通用的函數數組

//values: () => Promise<any>[]
function promiseReduce(values) {
    return values.reduce(
        (p, n) =>
            (p = p.then(
                () => n(),
                err => err
            )),
        Promise.resolve() //默認值加上
    );
}
複製代碼

若是我要將每次串行請求的結果收集到數組中,怎麼寫訥

方法一:簡單粗暴

new Promise(res => {
    let result = [];
    ajax(1)
        .then(res => {
            result.push(res);
            return ajax(2);
        })
        .then(res1 => {
            result.push(res1);
            return ajax(3);
        })
        .then(res2 => {
            result.push(res2);
            return ajax(4);
        })
        .then(res3 => {
            result.push(res3);
            res(result);
        })
        .catch(err => err);
}).then(data => {
    console.log(data);
});
複製代碼

方法二:每次再請求下一個時,添油加醋一番,別有滋味😋

let result = [];
ajax(1)
    .then(val => Promise.resolve(result.push(val)))
    .then(() => ajax(2))
    .then(val => Promise.resolve(result.push(val)))
    .then(() => ajax(3))
    .then(val => Promise.resolve(result.push(val)))
    .then(() => ajax(4))
    .then(val => Promise.resolve(result.push(val)))
    .then(() => {
        console.log(result, 'f');
    })
    .catch(err => err);
複製代碼

改進上面代碼promise

  1. 最直觀的反應就是若是list = [() => ajax(), () => ajax(), ...],那就將其改爲[() => ajax(), Promise.resolve(result.push(val))), () => ajax(), Promise.resolve(result.push(val))),...]
  2. 不過咱們能夠改進一下promiseReduce
function g(val) {
    val && result.push(val);
}
function promiseReduce(values, fn) {
    return values.reduce((p, n) => {
        return (p = p.then(
            val => Promise.resolve((fn(val), val)).then(res => n(res)),
            err => err
        ));
    }, Promise.resolve());
}
//也能夠這樣寫
function promiseReduce1(values, fn) {
    return values.reduce((p, n) => {
        return (p = p.then(
            val => Promise.resolve((fn(val), val)),
            err => err
        )).then(() => n());
    }, Promise.resolve());
}
複製代碼

對於 reject 的狀況,能夠每一個 promise 兜底,也能夠最後兜底。可是結果不同 調試代碼以下bash

function ajax(val) {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res(val);
        }, 1000);
    });
}
function g(val) {
    val && result.push(val);
}
function promiseReduce(values, fn) {
    return values.reduce((p, n) => {
        return (p = p.then(
            val => Promise.resolve((fn(val), val)).then(res => n(res)),
            err => err
        ));
    }, Promise.resolve());
}
function reject(val) {
    return new Promise((res, rej) => {
        setTimeout(() => {
            rej('error');
        }, 1000);
    });
}
//最後兜底
// function promiseReduce(values, fn) {
// return values.reduce((p, n) => {
// return (p = p.then(val => Promise.resolve((fn(val), val)))).then(() => n());
// }, Promise.resolve());
// }
promiseReduce(
    [() => reject(), () => ajax(1), () => ajax(2), () => ajax(3), , () => ajax(4), () => ajax(5)],
    g
)
    .then(val => {
        result.push(val);
        console.log(result);
    })
    .catch(err => {
        console.log(err);
    });
複製代碼

方法三:其實promise.all,也能夠改爲串行

//每隔兩秒去請求
function ajax(val) {
    return new Promise((resovle, reject) => {
        setTimeout(() => {
            resovle(val);
            console.log(val, 'val');
        }, val * 1000);
    }).catch(err => err);
}
let l = [ajax, ajax, ajax, ajax, ajax];
let config = 2;
let p = Promise.resolve(config);
function fn(p) {      
    return function (e, i) {
        return (p = p.then(res => e(res)));    //這邊玩法挺多的😄,有待玩耍
    };
}
l = l.map(fn(p));
Promise.all(l).then(res => {
    console.log(res);
});
複製代碼

利用promise.all能夠用來實現限流能夠看這篇文章,頗有意思

我羅列了幾個關鍵點,歡迎食用😋併發

  1. 保障每一個都返回promise,後面都加入回調隊列中
build (fn) {
    if (this.count < this.limit) {
      return this.run(fn)
    } else {
      return this.enqueue(fn)
    }
  }
複製代碼
  1. 沒到達限流前先併發執行,一個完成從回調隊列中拿來
async run (fn) {
    this.count++
    // 維護一個計數器
    const value = await fn()
    this.count--
    // 執行完,看看隊列有東西沒
    this.dequeue()
    return value
  }
複製代碼
  1. 下面這段代碼能作到,app

    其一:利用達到限流後加入的promise新的promise,放入promise.all中,異步

    其二:新的promise 它的狀態改變後返回的結果,也就是最後promise.all()返回到數組中的結果元素

    其三: 新的promise 將狀態控制權放入回調隊列中,等待去執行this.run(fn).then(resolve).catch(reject)

enqueue (fn) {
    //新搞一個promise
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject })
    })
  }
  dequeue () {
    if (this.count < this.limit && this.queue.length) {
      const { fn, resolve, reject } = this.queue.shift()
      //await fn() 執行完將結果帶入resolve(value)並結束其狀態
      this.run(fn).then(resolve).catch(reject)
    }
  }
複製代碼

核心

核心我認爲是:promise的進度條怎麼控制或者怎麼轉移,你們都知道是經過resolve或者reject。怎麼控制,怎麼玩它就是很大的學問了;每一個人的想法思路不一樣,各類組合實現就成了萬花筒,妙哉妙哉

簡單理解一下

由於一個promise的自身的狀態是惟一的,由pending開始到resolved或者rejected(這邊用這兩個名詞代替); then中的返回值是一個接力棒(並始終返回一個新的promise);又是一個新的開始。進度條控制也就轉交給了它。 axios 中間件,async-await 仿寫,限流等都有用到

以前的遇到的題

頁面上有一個輸入框,兩個按鈕,A 按鈕和 B 按鈕,點擊 A 或者 B 分別會發送一個異步請求,請求完成後,結果會顯示在輸入框中。

我以前第一想法是 維護一份回調隊列,每次點擊往隊列中加入回調。開關的控制在第一次這個思路相似 vue 的合併更新策略。其實這個思路是錯的。理由以下: 主要緣由就是:

  1. js去執行a.click(),b.click()用戶點擊按鈕a,按鈕b實際上是不同的,
  2. 前者統一先執行往主線程代碼也就是x先a.click(),其中有微任務的加到微任務隊列中,有宏任務的加入宏任務中;再去執行b.click()同理;主線程完事了,再去微任務,而後宏任務;
  3. 後者:先執行完a.click()的全部任務,而後再去執行b.click()

有興趣能夠看看這篇文章 👍

2.正確解法

const p = Promise.resolve();
function AClick() {
    p = p.then(() => {
        return new Promise((resolve, reject) => {
            ajax().then(() => {
                resolve();
                console.log('A');
            });
        });
    });
}
function BClick() {
    p = p.then(() => {
        return new Promise((resolve, reject) => {
            ajax().then(() => {
                resolve();
                console.log('B');
            });
        });
    });
}
複製代碼

若是按鈕不少能夠寫個函數對每一個按鈕包裝一層,相似裝飾器

總結

  1. 多看多理解,代碼的魅力亦或是組合
  2. 看懂了組合,下一步就是掌握其核心的本質
相關文章
相關標籤/搜索