Promise 剖析

本文针对 Promise 对象的原理进行分析,来实现一个 Promise 对象。

首先我们要理解什么 是 Promise ?

Promises 对象是 CommonJS 工作组提出的一种规范, 目的是为异步编程提供统一接口; 是 javascript 异步编程的重要概念,也是目前 javascript 主流的异步编程方法中的一种。

常见的 javascript 异步编程的有以下四种方法:
  1. callback
  2. 事件监听
  3. 发布/订阅 (观察者模式)
  4. Promise
我们常用的 callback 是异步操作最基本的方法
  • 举个例子,项目中要求前端 ajax 请求接口拿到产品订单号, 在根据产品订单号请求接口拿到 产品类型名称, 在根据产品的类型请求接口拿到产品的 code ,代码大概如下

    $.ajax({
        url: "",
        data: 1,
        success: function (orderId) {
            $.ajax({
                url: "",
                data: orderId,
                success: function (code) {
                    $.ajax({
                        url: "",
                        data: code,
                        success: function (c) {
                            console.log(c);
                        }
                    })
                }
            })
        }
    });
    • 这种写法每个请求之间相互依赖,层层嵌套形成了‘ 回调地狱 ’, 若在加上业务代码, 可读性与可维护性可想而知。而 Promise 的出现简化这种 回调地狱 式的编程模式, 它可以处理多个相互关联的异步操作,将异步代码抽离出来,实现了回调函数链式写法。
    // 代码改编之后,  让流程看起来更加清晰
    let promise = new Promise(function(resolve, reject){
            $.ajax({
                url: "",
                data: 1,
                success: function (data) {
                    resolve(data);
                }  
            });
        });
    promise.then( data1 => {
        $.ajax({
            url: "",
            data: data1,
            success: function (data) {
                return data
            }  
        });
    } ).then( data2 => {
        console.log( 'data2', data2 );
    } ).catch(err => {
        console.log( 'err', err );
    });
Promise 的用法
  1. Promise 是一个对象/构造函数,new Promise 传入 executor 执行函数作为参数,executor 函数接受两个函数 resolve, reject 作为参数, 返回一个 promise 实例

    let promise = new Promise(function(resolve, reject){
        // 处理一些异步的操作, 成功 执行 resolve, 失败执行 reject
        resolve('成功');
    });
  2. promise 有三种状态
    - 默认态为 pending(等待态)
    - 成功态为 fulfilled
    - 失败态为 rejected
    - promise 对象的默认状态为 pending, 当调用 resolve 时, 状态由 pending => fulfilled; 当调用 reject 时, 状态由 pending => rejected; 状态由 pending 装为 fulfilled 或 rejected 时,状态转变之后不能更改

  3. Promise 实例上 存在一个 then 方法,可以调用 then 方法来处理回调操作

    // then 方法接受两个参数, onFulfilled, onRejected ,都为可选参数
    // then 方法执行后返回一个 promise 实例, 可以实现链式调用
    promise.then( data => {
        // promise 执行结束后(resolve)当前函数会被调用,dataresolve 执行时传入的参数
        console.log(data);
    
    }, err => {
        // promise 执行被拒绝后(reject)当前函数会被调用,errresolve 被拘的理由
        console.log(err);
    
    } ).then();
  4. promise 实例上存在一个 catch 的方法, 用来捕获抛出的错误

    promise.catch(onRejected) 相当于 promise.then(null, onRejected);
    promise.then(onFulfilled, onRejected) onRejected 不能捕获 onFulfilled 中抛出的错误, 但 catch 可以捕获到

Promise 上还存在一些 静态方法
/**
 *  Promise.all 执行的结果全部为 fulfilled 时, 才会走到 then 中成功回调 
 *  参数接受一个数组, 数组中的每一项需为一个 promise 实例
 *  通常用来处理多个并行的异步操作
 */
function read (url) {
    return new Promise(function(resolve, reject){
        require('fs').readFile(url, 'utf-8', function(err, data){
            if (err) reject(err);
            resolve(data);
        })
    });
}
Promise.all([read('./1.txt'), read('./2.txt')]).then(data => {
    // ...
}, err => {
    // ...
});

/**
 *  Promise.race 执行的结果有一个为 rejected 时, 就会调用 then 中的失败回调
 *  参数接受一个数组, 数组中的每一项需为一个 promise 实例
 */
Promise.race([read('./1.txt'), read('./2.txt')]).then(data => {
    // ...
}, err => {
    // ...
});


/**
 * Promise.resolve
 * 类似语法糖 返回一个成功态的 promise
 */
Promise.resolve(value).then(onFulfilled);

/**
 * Promise.reject
 * 类似语法糖 返回一个失败态的 promise
 */
Promise.reject(reason).then(null, onRejected);
Promise 代码分析
function Promise (executor) {
    // executor 函数执行器, 是同步执行的
    let self = this;                // 防止 this 指针出现问题
    self.status = 'pending';        // promise 默认的状态
    self.value = undefined;         // 存储 fulfilled 时对应的值
    self.reason = undefined;        // 存储 fulfilled 时对应的原因
    self.onFulfilledCallbacks = []; // 存放 fulfilled 状态时 then 中 onFulfilled 的回调函数 
    self.onRejectedCallbacks = [];  // 存放 rejected 状态时 then 中 onRjected 的回调函数

    // 内部定义 成功时 执行的 函数
    function resolve (value) {

        // 根据 PromiseA+ 规划中 1.3 描述 value 可以是 thenable,这时需递归处理, 直到 value 为普通的值
        if ( value instanceof Promise ) {
            return value.then(resolve, reject);
        }

        // 根据 PromiseA+ 规范 2.1 描述 状态在 pending 时可以转为 成功或失败
        if (self.status === 'pending') {
            self.status = 'fulfilled';
            self.value = value;
            // 让存储的 onFulfilled 的回调依次执行
            self.onFulfilledCallbacks.forEach(function (fn) {
                fn();
            });
        }

    }

    // 内部定义 成功时 执行的 函数
    function reject (reason) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
            // 让存储的 onRjected 的回调依次执行
            self.onRejectedCallbacks.forEach( function (fn) {
                fn()
            } );
        }
    }

    // 捕获 executor 函数执行器执行时抛出的错误
    try {
        executor(resolve, reject);
    } catch (e) { 
        reject(e);
    }

}

/**
 * 注册 fulfilled 状态 / rejected 状态时对应的回调函数
 * @param {function} onFulfilled  状态为 fulfilled 时执行的回调
 * @param {function} onRjected    状态为 rejected 时执行的回调
 */
Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    let promise2;
    // 根据PromiseA+ 规范2.2.1描述, onFulfilled / onRjected是可选参数,因此可以存在值穿透的情况
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) { return value };
    onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err }; 

    if (self.status === 'fulfilled') {
        // 根据 PromiseA+ 规范2.2.7描述, then 方法的返回值必须是一个 promise 对象, 可是实现链式调用, 因此 then 方法执行后需返回一个新的 promise
        promise2 = new Promise(function (resolve, reject) {

            // 根据 PromiseA+ 规范 2.2.4 描述 ,onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调(被调用的那一轮事件循环之后的新执行栈中执行)
            setTimeout(function(){
                try {
                    // onFulfilled 执行的结果可能是一个普通值,或是一个新 promise 对象或函数,或是调用别人的 promise,需要进行判断,针对不同的值进行处理
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        });
    }

    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })

        })
    }

    if (self.status === 'pending') {
        // 如果 执行器 executor 中有异步操作而 promise 的状态属于 pending 时,这时不知道会转化为 fulfilled 还是 rejected,可以先将 onFulfilled 跟 onRjected 存在定义好的数组中, 当 pending 状态转变时, 执行对应的 callback
        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCallbacks.push(function () {
                setTimeout(function(){
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        });
    }

    return promise2;
}

/**
 * 针对 fulfilled 状态 / rejected 状态时执行的结果 x 进行统一处理
 * @param {promise} promise2 
 * @param {*} x       
 * @param {function} resolve  promise2 fulfilled 时执行的 resolved
 * @param {funtion}  reject   promise2 rejected 时执行的 reject
 */
function resolvePromise (promise2, x, resolve, reject) {
    let called; // 有的 promise 中会调用两个状态对应的回调,这时需定义一个标识,resolve 与 reject 只执行一个

    // 根据 promiseA+ 规范 2.3.1 描述 如果 x = promise2 , 抛出类型错误
    if (promise2 === x) {
        return reject(new TypeError('循环引用'));
    }

    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 检测 x 上是否存在 then 函数
        try {
            let then = x.then;

            if (typeof then === 'function') {
                then.call(x, function (y) { // PromiseA+ 2.3.3
                    if (called) return;
                    called = true;
                    // 此时的 y 可能还是一个 promise, 再去解析直到返回的是一个普通值
                    resolvePromise(promise2, y, resolve, reject); 
                }, function (err) {
                    // rejected 态 
                    if (called) return
                    called = true
                    reject(err);
                });
            }
            else {
                resolve(x);
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    }
    else {
        // 如果 x 是一个普通值的话,promise2 走向 fulfilled 态
        resolve(x);
    }
}

/**
 * 捕获错误的方法
 * @param {function} callback  状态为 rejected 时执行的回调
 */
Promise.prototype.catch = function (callback) {
    return this.then(null, callback);
}

// ----------------------  Promise 上的静态方法  ------------------------

/**
 * 执行的结果全部为 fulfilled 状态时, 才会走到 then 中 onFulfilled 对应的回调 
 * @param {*} promiseAry  接受一个数组,数组中的每一项需为一个 promise 实例, 返回一个新的 promise 实例
 * 通常用来处理多个并行的异步操作
 */
Promise.all = function (promiseAry) {
    let arr = [];
    let i = 0;

    return new Promise(function (resolve, reject){

        function processData (index, y) {
            arr[index] = y;
            if ( ++i === promiseAry.length ) {
                resolve(arr);
            }
        }

        for ( let i = 0; i < promiseAry.length; i++ ) {
            promiseAry[i].then(function (y) {
                processData(i, y);
            }, reject)
        }
    });
}

/**
 * Promise.race 执行的结果有一个为 rejected 时, 就会调用 then 中的 onReject 对应的回调
 * @param {*} promiseAry  参数接受一个数组,数组中的每一项需为一个 promise 实例, 返回一个新的 promise 实例
 */
Promise.race = function (promiseAry) {

    return new Promise(function (resolve, reject) {
        for (let i = 0; i < promiseAry.length; i++) {
            promiseAry[i].then(resolve, reject);
        }
    });
}

/**
 * 语法糖, 返回一个 状态为 fulfilled 时的 promise 实例
 * @param {*} value 
 */
Promise.resolve = function (value) {
    return new Promise(function(resolve, reject){
        resolve(value);
    });
}

/**
 * 语法糖, 返回一个 状态为 rejected 时的 promise 实例
 * @param {*} reason 
 */
Promise.reject = function (reason) {
    return new Promise(function (resolve, reject) {
        reject(reason);
    });
}

/** 
 * 在 Promise 上增加一个 deferred 方法, 
 * 返回的 deferred 对象上拥有一个 Promise 对象, 具有对 Promise 状态操作的方法 
 * 参考 jQuery 上 deferred 的实现
 * 参考文档 http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object
*/
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });

    return dfd;
}

可以下载一个基于 PromiseA+规范的 Promise 测试库,
npm install promises-aplus-tests -g
promises-aplus-tests 文件名
参考文档:
PromiseA+规范
PromiseA+规范 译文
deferred
Promise对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图像识别技术在病虫害检测中的应用是一个快速发展的领域,它结合了计算机视觉和机器学习算法来自动识别和分类植物上的病虫害。以下是这一技术的一些关键步骤和组成部分: 1. **数据收集**:首先需要收集大量的植物图像数据,这些数据包括健康植物的图像以及受不同病虫害影响的植物图像。 2. **图像预处理**:对收集到的图像进行处理,以提高后续分析的准确性。这可能包括调整亮度、对比度、去噪、裁剪、缩放等。 3. **特征提取**:从图像中提取有助于识别病虫害的特征。这些特征可能包括颜色、纹理、形状、边缘等。 4. **模型训练**:使用机器学习算法(如支持向量机、随机森林、卷积神经网络等)来训练模型。训练过程中,算法会学习如何根据提取的特征来识别不同的病虫害。 5. **模型验证和测试**:在独立的测试集上验证模型的性能,以确保其准确性和泛化能力。 6. **部署和应用**:将训练好的模型部署到实际的病虫害检测系统中,可以是移动应用、网页服务或集成到智能农业设备中。 7. **实时监测**:在实际应用中,系统可以实时接收植物图像,并快速给出病虫害的检测结果。 8. **持续学习**:随着时间的推移,系统可以不断学习新的病虫害样本,以提高其识别能力。 9. **用户界面**:为了方便用户使用,通常会有一个用户友好的界面,显示检测结果,并提供进一步的指导或建议。 这项技术的优势在于它可以快速、准确地识别出病虫害,甚至在早期阶段就能发现问题,从而及时采取措施。此外,它还可以减少对化学农药的依赖,支持可持续农业发展。随着技术的不断进步,图像识别在病虫害检测中的应用将越来越广泛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值