小玩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. 看懂了组合,下一步就是掌握其核心的本质
相关文章
相关标签/搜索