js异步处理方案,以及promise的运作流程解析

426 阅读7分钟

异步的概念

  • Javascript语言的执行环境是"单线程",所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
  • 这种模式的好处是实现起来比较简单;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。
  • 为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
  • 异步编程的代码不会阻塞后续代码的执行,不是有序执行的,对于操作一些耗时操作执行效率更高

什么时候使用异步

  1. 事件监听函数的回调
  2. 定时器函数的回调
  3. 网络请求

实现异步编程

回调函数

  • 有这样一个场景,有两个函数f1,f2,f2的执行需要等待f1的结果,而f1的执行是一个耗时的操作,就可以将f2作为f1的回调函数
  function f2(){}
 function f1(callback){
    // 异步执行f1,先去执行其他逻辑
    setTimeout(()=>{
      // f1的任务代码
           // ...
           // 等到f1逻辑执行完毕后执行f2
      callback();
    })
 }
 f1(f2);
  • 采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
  • 回调函数的优点是简单、容易理解,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱,多层的嵌套会造成“回调地狱”。

事件监听

  • 采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
  • 下面代码使用jquery语法
   // 为f2绑定done事件,执行f2函数
  f1.on('done', f2);
  function f1(){
    setTimeout(function () {
      // f1的任务代码
            // 触发done事件,由于f1监听了done,则会执行f2函数
      f1.trigger('done');
    }, 1000);

  }
  • 这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,有利于实现模块化
  • 缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

发布/订阅

  • 我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern)。
  • 流程应该是先订阅,再发布
  // jQuery 
 jQuery.subscribe("done", f2);
 function f1(){
    setTimeout(function () {
      // f1的任务代码
      jQuery.publish("done");
    }, 1000);
  }
  • 这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

promise

  • 通过new Promise(resolve,reject){}创建一个promise实例,resolve/reject是两个函数,每个promise实例拥有then方法,then方法有两个函数参数,第一个参数是成功状态触发,第二个函数是失败状态触发,当resolve/reject函数执行会触发then函数中的回调函数的执行
使用es5语法实现一个符合promiseA+规范的库
(function () {
    var PENDING = "pending",
        FULFILLED = "fulfilled",
        REJECTED = "rejected";

    function Promise(executor) {
        if (typeof executor !== "function") throw new TypeError("promise resolve " + executor + " is not a  function");
        // 保存this
        var self = this;
        self.PromiseState = PENDING;
        self.PromiseResult = undefined;
        // 储存onFulfilled/onRejected未执行的回调函数
        self.onFulfilledCallbacks = [];
        self.onRejectedCallbacks = [];
        var run = function (state, result) {
            // 如果当前状态为pending,不做任何处理
            if (self.PromiseState !== PENDING) return;
            // 不是pending,改变当前promise实例的状态
            self.PromiseState = state;
            // 改变当前promise实例的值
            self.PromiseResult = result;
            // 异步通知后续then回调函数执行e4
            setTimeout(function () {
                // 执行目标函数数组的所有函数
                var callbackChains = state === FULFILLED ? self.onFulfilledCallbacks : self.onRejectedCallbacks;
                for (var i = 0; i < callbackChains.length; i++) {
                    // 如果是函数,执行函数并将当前promise实例的值作为参数传入
                    if (typeof callbackChains[i] === "function") {
                        callbackChains[i](self.PromiseResult);
                    }
                }
            });
        };
        // 失败态回调
        var reject = function reject(reason) {
            run(REJECTED, reason);
        };
        // 成功态回调
        var resolve = function resolve(value) {
            // 处理resolve(new Promise(...))的情况
            // 当value 已决状态后调用resolve/reject,并将已决结果转递给resolve/reject函数
            if (value instanceof Promise) {
                return value.then(resolve, reject);
            }
            run(FULFILLED, value);
        };
        // 立即执行executor,如果函数报错,promise状态也要改成reject状态
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    // 统一处理基于then返回新实例的成功和失败
    function resolvePromise(promise2, x, resolve, reject) {
        // 阻止循环引用
        if (x === promise2) {
            return reject(new TypeError("Chain cycle deleted for promise #<Promise>"));
        }
        // 如果结果x是对象或者函数
        if ((x !== null && typeof x === "object") || typeof x === "function") {
            // 用于防止第三方promise在已决状态后修改状态
            let called = false;
            try {
                // 获取对象的then属性
                var then = x.then;
                // 如果then是一个函数,x则是一个promise实例,等待已决状态结果
                if (typeof then === "function") {
                    // 返回结果是一个新的promise实例【可能是别人构建的promise】
                    then.call(x, function (y) {
                        // 如果已经调用过,就不再执行后续
                        if (called) return;
                        called = true;
                        // 已决状态
                        resolvePromise(promise2, y, resolve, reject);
                    }, function (r) {
                        // 如果已经调用过,就不再执行后续
                        if (called) return;
                        called = true;
                        // 拒绝状态
                        reject(r);
                    });
                }
                // then不是一个函数,x是一个普通对象,直接resolve x
                else {
                    resolve(x);
                }
            } catch (err) {
                if (called) return;
                called = true;
                reject(err);
            }
        }
        // 结果x是普通类型,直接resolve
        else {
            resolve(x);
        }
    }

    Promise.prototype = {
        // 标记为自定义类
        customize: true,
        constructor: Promise,
        then: function (onFulfilled, onRejected) {
            // 处理onFulfilled/onRejected不传递的情况,顺延穿透效果
            if (typeof onFulfilled !== "function") {
                onFulfilled = function (value) {
                    return value;
                };
            }
            if (typeof onRejected !== "function") {
                onRejected = function (reason) {
                    throw reason;
                };
            }
            var self = this;
            var promise = new Promise(function (resolve, reject) {
                // 如果当前promise实例的状态是已决成功状态
                if (self.PromiseState === FULFILLED) {
                    // 异步执行
                    setTimeout(function () {
                        try {
                            // 获取回调函数的返回值
                            var x = onFulfilled(self.PromiseResult);
                            // 交给resolvePromise处理
                            resolvePromise(promise, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                }
                // 如果当前promise实例是已决失败状态
                else if (self.PromiseState === REJECTED) {
                    setTimeout(function () {
                        try {
                            var x = onRejected(self.PromiseResult);
                            resolvePromise(promise, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                } else {
                    // 当前为pending状态
                    self.onFulfilledCallbacks.push(function (PromiseResult) {
                        setTimeout(function () {
                            try {
                                var x = onFulfilled(PromiseResult);
                                resolvePromise(promise, x, resolve, reject);
                            } catch (err) {
                                reject(err);
                            }
                        });
                    });
                    self.onRejectedCallbacks.push(function (PromiseResult) {
                        setTimeout(function () {
                            try {
                                var x = onRejected(PromiseResult);
                                resolvePromise(promise, x, resolve, reject);
                            } catch (err) {
                                reject(err);
                            }
                        });
                    });
                }
            });
            return promise;
        },
        catch: function (onRejected) {
            return this.then(null, onRejected);
        },
        finally: function (fn) {
            // 执行回调,穿透到下一个then/catch
            return this.then(data => {
                return Promise.resolve(fn()).then(() => data);
            }, err => {
                return Promise.resolve(fn()).then(() => {
                    throw err;
                });
            });
        }
    };
    Promise.resolve = function (value) {
        return new Promise(function (resolve) {
            resolve(value);
        });
    };
    Promise.reject = function (reason) {
        return new Promise(function (_, reject) {
            reject(reason);
        });
    };
    Promise.all = function (promiseArr) {
        return new Promise(function (resolve, reject) {
            var index = 0,
                ret = [];
            // 循环遍历promise实例数组
            for (var i = 0; i < promiseArr.length; i++) {
                // 创建闭包,保存i
                (function (i) {
                    // 取出每一个promise实例
                    var proItem = promiseArr[i];
                    // 如果元素不是promise对象,直接将对应数组位置赋值为当前值,index记录加一
                    if (!(proItem instanceof Promise)) {
                        index++;
                        ret[i] = proItem;
                        if (index === promiseArr.length) {
                            // 将最终的结果数组resolve
                            resolve(ret);
                        }
                        return;
                    }
                    // 当前proItem是promise实例,调用then方法获取resolve/reject处理结果
                    proItem.then(function (result) {
                        index++;
                        // 将获取的结果赋值给数组对应位置
                        ret[i] = result;
                        // 如果index记录等于promise实例数组长度,说明所有promise实例已经处理完毕
                        if (index === promiseArr.length) {
                            // 将最终的结果数组resolve
                            resolve(ret);
                        }
                    }).catch(function (err) {
                        // 只要有一个失败就整体失败
                        reject(err);
                    });
                }(i));
            }
        });
    };
    Promise.race = function (promiseArr) {
        return new Promise((resolve, reject) => {
            for (var i = 0; i < promiseArr.length; i++) {
                (function (i) {
                    var proItem = promiseArr[i];
                    if (!(proItem instanceof Promise)) {
                        resolve(proItem);
                    } else {
                        // 是promise实例
                        proItem.then(function (result) {
                            resolve(result);
                        }, function (err) {
                            reject(err);
                        });
                    }
                }(i));
            }
        });
    };

    if (typeof globalThis === "object") globalThis.Promise = Promise;
    else if (typeof window === "object" && window.window === window) window.Promise = Promise;
    else if (typeof global === "object") global.Promise = Promise;
}());
promise的并发操作 createRequestPool函数实现
const delay = function (interval) {
    return new Promise(function (resolve, reject) {
        if (interval === 1000) {
            reject(new Error(interval));
        }
        setTimeout(function () {
            resolve(interval);
        }, interval);
    });
};

const tasks = [
    () => delay(1002),
    () => delay(1004),
    () => delay(100),
    () => delay(1302),
    () => delay(1092),
    () => delay(1024)
];
// 创建请求池 tasks为任务列表,每个元素是一个函数,函数执行返回promise实例,pool是并发数量
function createRequestPool(tasks, pool = 5) {
    // 创建并发请求数组
    let togetherPool = new Array(pool).fill(0);
    // 记录索引,保持result位置和任务位置对应
    let index = 0;
    // 结果数组
    let result = [];
    // 返回pool个promise并发执行
    togetherPool = togetherPool.map(() => {
        return new Promise(function (resolve, reject) {
            const run = function () {
                // 如果没有请求了,执行resolve
                if (index >= tasks.length) {
                    resolve();
                    return;
                }
                // 保留索引,用于result结果保持相对位置
                let old_index = index;
                // 获取当前索引的task函数
                const task = tasks[index++];
                task().then(function (res) {
                    // 将任务结果放入result并运行下一个任务
                    result[old_index] = res;
                    run();
                }).catch(function (err) {
                    // 发生异常,直接reject
                    reject(err);
                });
            };
            // 直接执行run函数
            run();
        });
    });
    // 得到结果返回
    return Promise.all(togetherPool).then(() => result);
}
// 测试用例
let res = createRequestPool(tasks, 2).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
});
面试题技巧
  • promise在面试过程中也是常考的
  • 首先要弄清事件循环宏任务微任务的概念
  • 遇到setTimeout,等待时间到了就会将回调放入宏任务队列中
  • 遇到promise,promise的回调函数execute会同步执行,then的回调函数会放到微任务队列中
  • 遇到async await,await下面的代码相当于promise实例中then的回调函数,放到微任务中
  • 执行流程
  1. 首先执行同步任务
  2. 同步任务执行过程中会将异步任务放到宏队列/微队列
  3. 同步任务完成后,首先按照入队顺序执行微任务队列的任务,如果过程中产生了新的异步任务,则将新的异步任务加入对应的任务队列
  4. 同步任务和微任务队列清空后,首先按照入队顺序执行宏任务队列的任务,如果这个过程中产生了新的微任务,会将新的微任务放到微队列中,执行完同步代码后,要先去清空微队列,当没有同步任务和微任务后,才能继续进行清空宏任务队列操作
  5. 注意每次执行一个宏任务前,都要保证微任务队列是空的,有微任务就先执行微任务

做做题吧

promise面试题连接,点击跳转~