Promise机制

 Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval、DOM事件机制、ajax,通过传入回调函数实现控制反转。异步编程为js带来强大灵活性的同时,也带来了嵌套回调的问题。详细来说主要有两点,第一嵌套太深代码可读性太差,第二并行逻辑必须串行执行。

request = function(url, cb, eb) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
                cb(xhr.responseText);
            } else {
                eb(new Error({
                    message: xhr.status
                }));
            }
        }
    };
    xhr.open(‘get‘, url, true);
    xhr.send(null);
}

  

  这个例子中程序要依次处理data1、data2、data3,嵌套太多可读性太差

request = function(url) {
    var def = new Deferred();

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
                def.resolve(xhr.responseText)
            } else {//简化ajax,没有提供错误回调
                def.reject(new Error({
                    message: xhr.status
                }));
            }
        }
    };
    xhr.open(‘get‘, url, true);
    xhr.send(null);

    return def.promise;
}

request(‘data1.json‘).then(function(data1) {
    console.log(data1);//处理data1
    return request(‘data2.json‘);
}).then(function(data2) {
    console.log(data2);//处理data2
    return request(‘data3.json‘);
}, function(err) {
    console.error(err);
}).then(function(data3) {
    console.log(data3);
    alert(‘success‘);
}, function(err) {
    console.error(err);
});

  

  这个例子中程序需要请求data1、data2、data3数据,得到三个数据后才进行下一步处理。数据并不需要串行请求,但我们的代码却需要串行执行,增加了等待时间。

//并行逻辑串行执行
request(‘data1‘, function(data1) {
    request(‘data2‘, function(data2) {
        request(‘data3‘, function(data3) {
            console.log(data1, data2, data3);//处理全部数据

            alert(‘success‘);
        }, function(err) {
            console.error(err);
        });
    }, function(err) {
        console.error(err);
    });
}, function(err) {
    console.error(err);
});

  

Promise机制

  Promise机制便是上述问题的一种解决方案。与他相关的规范有PromiseAPromiseA+,PromiseA中对Promise进行了整体描述,PromiseA+对A进行了补充,在then函数的行为方面进行了更加详尽的阐述。

PromiseA+规范

promise represents the eventual result of an asynchronous operation.

一个promise代表了一个异步操作的最终结果

The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.跟promise交互的主要方式是通过他的then方法来注册回调函数去接收promise的最终结果值或者是promise不能完成的原因。

  我们可以简单总结一下规范。每个promise都有三个状态:pending(默认)、fulfilled(完成)、rejected(失败);默认状态可以转变为完成态或失败态,完成态与失败态之间无法相互转换,转变的过程是不可逆的,转变一旦完成promise对象就不能被修改。通过promise提供的then函数注册onFulfill(成功回调)、onReject(失败回调)、onProgres(进度回调)来与promise交互。Then函数返回一个promise对象(称为promise2,前者成为promise1),promise2受promise1状态的影响,具体请查看A+规范。

  上两个规范中并没有说明promise的状态如何改变,大部分前端框架中使用Deferred来改变promise的状态(resolve()、reject())。二者关系请看下图。

  这里根据规范,我们实现一下promise

Promise = function() {
    this.queue = [];
    this.value = null;
    this.status = ‘pending‘;// pending fulfilled rejected
};

Promise.prototype.getQueue = function() {
    return this.queue;
};
Promise.prototype.getStatus = function() {
    return this.status;
};
Promise.prototype.setStatus = function(s, value) {
    if (s === ‘fulfilled‘ || s === ‘rejected‘) {
        this.status = s;
        this.value = value || null;
        this.queue = [];
        var freezeObject = Object.freeze || function(){};
        freezeObject(this);// promise的状态是不可逆的
    } else {
        throw new Error({
            message: "doesn‘t support status: " + s
        });
    }
};
Promise.prototype.isFulfilled = function() {
    return this.status === ‘fulfilled‘;
};
Promise.prototype.isRejected = function() {
    return this.status === ‘rejected‘;
}
Promise.prototype.isPending = function() {
    return this.status === ‘pending‘;
}
Promise.prototype.then = function(onFulfilled, onRejected) {
    var handler = {
        ‘fulfilled‘: onFulfilled,
        ‘rejected‘: onRejected
    };
    handler.deferred = new Deferred();

    if (!this.isPending()) {//这里允许先改变promise状态后添加回调
        utils.procedure(this.status, handler, this.value);
    } else {
        this.queue.push(handler);//then may be called multiple times on the same promise;规范2.2.6
    }
    return handler.deferred.promise;//then must return a promise;规范2.2.7
};

var utils = (function(){
    var makeSignaler = function(deferred, type) {
        return function(result) {
            transition(deferred, type, result);
        }
    };

    var procedure = function(type, handler, result) {
        var func = handler[type];
        var def = handler.deferred;

        if (func) {
            try {
                var newResult = func(result);
                if (newResult && typeof newResult.then === ‘function‘) {//thenable
                    // 此种写法存在闭包容易造成内存泄露,我们通过高阶函数解决
                    // newResult.then(function(data) {
                    //     def.resolve(data);
                    // }, function(err) {
                    //     def.reject(err);
                    // });
                    //PromiseA+规范,x代表newResult,promise代表def.promise
                    //If x is a promise, adopt its state [3.4]:
                    //If x is pending, promise must remain pending until x is fulfilled or rejected.
                    //If/when x is fulfilled, fulfill promise with the same value.
                    //If/when x is rejected, reject promise with the same reason.
                    newResult.then(makeSignaler(def, ‘fulfilled‘), makeSignaler(def, ‘rejected‘));//此处的本质是利用了异步闭包
                } else {
                    transition(def, type, newResult);
                }
            } catch(err) {
                transition(def, ‘rejected‘, err);
            }
        } else {
            transition(def, type, result);
        }
    };

    var transition = function(deferred, type, result) {
        if (type === ‘fulfilled‘) {
            deferred.resolve(result);
        } else if (type === ‘rejected‘) {
            deferred.reject(result);
        } else if (type !== ‘pending‘) {
            throw new Error({
                ‘message‘: "doesn‘t support type: " + type
            });
        }
    };

    return {
        ‘procedure‘: procedure
    }
})();

Deferred = function() {
    this.promise = new Promise();
};

Deferred.prototype.resolve = function(result) {
    if (!this.promise.isPending()) {
        return;
    }

    var queue = this.promise.getQueue();
    for (var i = 0, len = queue.length; i < len; i++) {
        utils.procedure(‘fulfilled‘, queue[i], result);
    }
    this.promise.setStatus(‘fulfilled‘, result);
};

Deferred.prototype.reject = function(err) {
    if (!this.promise.isPending()) {
        return;
    }

    var queue = this.promise.getQueue();
    for (var i = 0, len = queue.length; i < len; i++) {
        utils.procedure(‘rejected‘, queue[i], err);
    }
    this.promise.setStatus(‘rejected‘, err);
}

  通过Promise机制我们的编程方式可以变成这样:

request = function(url) {
    var def = new Deferred();

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
                def.resolve(xhr.responseText)
            } else {//简化ajax,没有提供错误回调
                def.reject(new Error({
                    message: xhr.status
                }));
            }
        }
    };
    xhr.open(‘get‘, url, true);
    xhr.send(null);

    return def.promise;
}

request(‘data1.json‘).then(function(data1) {
    console.log(data1);//处理data1
    return request(‘data2.json‘);
}).then(function(data2) {
    console.log(data2);//处理data2
    return request(‘data3.json‘);
}, function(err) {
    console.error(err);
}).then(function(data3) {
    console.log(data3);
    alert(‘success‘);
}, function(err) {
    console.error(err);
});

  

  

  对于并行逻辑串行执行问题我们可以这样解决

//所有异步操作都完成时,进入完成态,
//其中一项异步操作失败则进入失败态
all = function(requestArray) {
    // var some = Array.prototype.some;
    var def = new Deferred();
    var results = [];
    var total = 0;
    requestArray.some(function(r, idx) {
        //为数组中每一项注册回调函数
        r.then(function(data) {
            if (def.promise.isPending()) {
                total++;
                results[idx] = data;

                if (total === requestArray.length) {
                    def.resolve(results);
                }
            }
        },  function(err) {
            def.reject(err);
        });
        //如果不是等待状态则停止,比如requestArray[0]失败的话,剩下数组则不用继续注册
        return !def.promise.isPending();
    });

    return def.promise;
}

all(
    [request(‘data1.json‘),
    request(‘data2.json‘),
    request(‘data3.json‘)]
    ).then(
        function(results){
            console.log(results);// 处理data1,data2,data3
            alert(‘success‘);
    }, function(err) {
        console.error(err);
    });

  

  以下是几个测试案例

//链式调用
var p1 = new Deferred();
p1.promise.then(function(result) {
    console.log(‘resolve: ‘, result);
    return result;
}, function(err) {
    console.log(‘reject: ‘, err);
    return err;
}).then(function(result) {
    console.log(‘resolve2: ‘, result);
    return result;
}, function(err) {
    console.log(‘reject2: ‘, err);
    return err;
}).then(function(result) {
    console.log(‘resolve3: ‘, result);
    return result;
}, function(err) {
    console.log(‘reject3: ‘, err);
    return err;
});
p1.resolve(‘success‘);
//p1.reject(‘failed‘);
p1.promise.then(function(result) {
    console.log(‘after resolve: ‘, result);
    return result;
}, function(err) {
    console.log(‘after reject: ‘, err);
    return err;
}).then(function(result) {
    console.log(‘after resolve2: ‘, result);
    return result;
}, function(err) {
    console.log(‘after reject2: ‘, err);
    return err;
}).then(function(result) {
    console.log(‘after resolve2: ‘, result);
    return result;
}, function(err) {
    console.log(‘after reject2: ‘, err);
    return err;
});

//串行异步
var p2 = new Deferred();
p2.promise.then(function(result) {
    var def = new Deferred();
    setTimeout(function(){
        console.log(‘resolve: ‘, result);
        def.resolve(result);
    })
    return def.promise;
}, function(err) {
    console.log(‘reject: ‘, err);
    return err;
}).then(function(result) {
    var def = new Deferred();
    setTimeout(function(){
        console.log(‘resolve2: ‘, result);
        def.reject(result);
    })
    return def.promise;
}, function(err) {
    console.log(‘reject2: ‘, err);
    return err;
}).then(function(result) {
    console.log(‘resolve3: ‘, result);
    return result;
}, function(err) {
    console.log(‘reject3: ‘, err);
    return err;
});
p2.resolve(‘success‘);

//并行异步
var p1 = function(){
    var def = new Deferred();
    setTimeout(function() {
        console.log(‘p1 success‘);
        def.resolve(‘p1 success‘);
    }, 20);

    return def.promise;
}
var p2 = function(){
    var def = new Deferred();
    setTimeout(function() {
        console.log(‘p2 failed‘);
        def.reject(‘p2 failed‘);
    }, 10);

    return def.promise;
}

var p3 = function(){
    var def = new Deferred();
    setTimeout(function() {
        console.log(‘p3 success‘);
        def.resolve(‘p3 success‘);
    }, 15);

    return def.promise;
}

all([p1(), p2(), p3()]).then(function(results) {
    console.log(results);
}, function(err) {
    console.error(err);
});

  

Promise优点

对比使用Promise前后我们可以发现,传统异步编程通过嵌套回调函数的方式,等待异步操作结束后再执行下一步操作。过多的嵌套导致意大利面条式的代码,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。

参考文章:

javascript 异步编程

jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

JavaScript异步编程原理

时间: 2024-10-04 17:34:24

Promise机制的相关文章

闲话Promise机制

Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval.DOM事件机制.ajax,通过传入回调函数实现控制反转.异步编程为js带来强大灵活性的同时,也带来了嵌套回调的问题.详细来说主要有两点,第一嵌套太深代码可读性太差,第二并行逻辑必须串行执行. 1 request = function(url, cb, eb) { 2 var xhr = new XMLHttpRequest(); 3 xhr.onreadystatec

Javascript异步编程之三Promise: 像堆积木一样组织你的异步流程

这篇有点长,不过干货挺多,既分析promise的原理,也包含一些最佳实践,亮点在最后:) 还记得上一节讲回调函数的时候,第一件事就提到了异步函数不能用return返回值,其原因就是在return语句执行的时候异步代码还没有执行完毕,所以return的值不是期望的运算结果. Promise却恰恰要回过头来重新利用这个return语句,只不过不是返回最终运算值,而是返回一个对象,promise对象,用它来帮你进行异步流程管理. 先举个例子帮助理解.Promise对象可以想象成是工厂生产线上的一个工人

Promise编程规范

参考: http://www.cnblogs.com/dojo-lzz/p/4340897.html 闲话promise机制  http://www.cnblogs.com/lvdabao/p/es6-promise-1.html 大白话讲解promise

Promise的前世今生和妙用技巧

浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一个线程中.因此对于长时间的耗时操作,将会阻塞UI的响应.为了更好的UI体验,应该尽量的避免JavaScript中执行较长耗时的操作(如大量for循环的对象diff等)或者是长时间I/O阻塞的任务.所以在浏览器中的大多数任务都是异步(无阻塞)执行的,例如:鼠标点击事件.窗口大小拖拉事件.定时器触发事件

【转】AngularJS领域中的Promise

Promise是一个接口,它用来处理的对象具有这样的特点:在未来的某一时刻(主要是异步调用) 会从服务端返回或者被填充属性.其核心是,promise是一个带有then()函数的对象. 为了展示它的优点,来看一个例子其中需要获取用户当前的配置文件: 1 var currentProfile=null; 2 var username='something'; 3 fetchServerConfig(function(serverConfig){ 4 fetchUserProfiles(serverC

《React-Native系列》3、RN与native交互之Callback、Promise

接着上一篇<React-Native系列>RN与native交互与数据传递,我们接下来研究另外的两种RN与Native交互的机制 一.Callback机制 首先Calllback是异步的,RN端调用Native端,Native会callback,但是时机是不确定的,如果多次调用的话,会存在问题. Naive端是无法主动通过回调函数向RN端发送消息的. 具体实现代码如下: Native端暴露好接口 @ReactMethod public void measureLayout(Callback e

ajax系列之用jQuery的ajax方法向服务器发出get和post请求

打算写个ajax系列的博文,主要是写给自己看,学习下ajax的相关知识和用法,以更好的在工作中使用ajax. 假设有个网站A,它有一个简单的输入用户名的页面,界面上有两个输入框,第一个输入框包含在一个form表单里用来实现form提交,第二个输入框是单独的.没有包含在form里,下面就用这两个输入框来学习下jQuery的ajax. 1,前端的html和javascript代码 页面html 1 <main style="text-align: center; margin: 200px a

浅谈开发模式及架构发展

一.传统开发模式 传统的开发模式基本一般是重服务端的开发方式,大部分工作都在服务端执行,然后返回到客户端(通常是HTML).以Asp.net MVC为例,如下图: #1 根据请求的路由定位到对应的Controller的对应的Action. #2 执行相关逻辑,得到结果Model(也可能没有Model,如直接返回View). #3 定位并加载对应的View(也可能没有View,如返回TextResult,JsonResult等). #4 在服务端通过Razor引擎把Model和View绑定起来生成

从2014年D2前端技术论坛看前端发展趋势

上周六有幸参加了在杭州阿里巴巴西溪园区举办的2014年D2前端技术论坛和晚上的酒会,实地感受了一下阿里巴巴前端开发的技术氛围和影响力,总体上看这次D2规模还是挺大的,国内前端的知名大牛基本上都到了. D2今年的主题是绽放,确实挺符合现在前端发展的阶段,随着对用户体验的不断追求,移动Web的迅猛发展,HTML5的普及,NodeJS的投入商用,AngularJS等新框架的出现,前端的重要性和工程化程度不断提高,上午会上有个妹子说2015年D2的主题是逆袭,我个人觉得挺合适的.从参会人数看,目测有接近