ES6 Promise

Posted by huangqing on August 5, 2017

ES6 Promise

问题

JavaScript的Callback机制深入人心。而ECMAScript的世界同样充斥的各种异步操作(异步IO、setTimeout等)。异步和Callback的搭载很容易就衍生”回调金字塔”。——由此产生Deferred/Promise。

Deferred起源于Python,后来被CommonJS挖掘并发扬光大,得到了大名鼎鼎的Promise,并且已经纳入ECMAScript 6(JavaScript下一版本)。

Promise/Deferred是当今最著名的异步模型,不仅强壮了JavaScript Event Loop(事件轮询)机制下异步代码的模型,同时增强了异步代码的可靠性。

有了 Promise ,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

概述

Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。

语法

new Promise(executor);
new Promise(function(resolve, reject) { ... });

例子:


var promise = new Promise(function(resolve, reject) { 
    if (/** 异步操作成功* */){ 
        resolve(value); 
    } else { 
        reject(error); 
    }
});

promise.then(function(value) { 
    // success
}, 

function(value) { 
    // failure
});

参数

executor带有 resolvereject 两个参数的函数对象。一旦我们的操作完成即可调用这些函数。

  • 第一个参数用在处理执行成功的场景
  • 第二个参数则用在处理执行失败的场景。

描述

Promise对象是一个返回值的代理,这个返回值在promise对象创建时未必已知。它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的 promise 对象来替代原返回值。

Promise对象有以下几种状态:

  • pending: 初始状态, 非 fulfilled 或 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失败的操作.

pending状态的promise对象既可转换为带着一个成功值的fulfilled 状态,也可变为带着一个失败信息的 rejected 状态。

特点

  • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 当状态发生转换时,promise.then绑定的方法(函数句柄)就会被调用。

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

当绑定方法时,如果 promise对象已经处于 fulfilled 或 rejected 状态,那么相应的方法将会被立刻调用, 所以在异步操作的完成情况和它的绑定方法之间不存在竞争条件。

因为Promise.prototype.then 和 Promise.prototype.catch 方法返回 promises对象, 所以它们可以被链式调用—— 一种被称为 composition的操作。

promises

注意: 如果一个promise对象处在fulfilled或rejected状态而不是pending状态,那么它也可以被称为settled状态。你可能也会听到一个术语resolved ,它表示promise对象处于settled状态,或者promise对象被锁定在了调用链中。关于promise的状态, Domenic Denicola 的 States and fates 有更多详情可供参考。

基本的api

  • Promise.resolve()
  • Promise.reject()
  • Promise.prototype.then()
  • Promise.prototype.catch()
  • Promise.all() 完成全部
  • Promise.race() 完成一个

常用的JavaScript的promise的写法

function get(uri){
    return http(uri, 'GET', null);
}

function post(uri,data){
    if(typeof data === 'object' && !(data instanceof String || (FormData && data instanceof FormData))) {
        var params = [];
        for(var p in data) {
            if(data[p] instanceof Array) {
                for(var i = 0; i < data[p].length; i++) {
                    params.push(encodeURIComponent(p) + '[]=' + encodeURIComponent(data[p][i]));
                }
            } else {
                params.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));
            }
        }
        data = params.join('&');
    }

    return http(uri, 'POST', data || null, {
        "Content-type":"application/x-www-form-urlencoded"
    });
}

function http(uri,method,data,headers){
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open(method,uri,true);
        if(headers) {
            for(var p in headers) {
                xhr.setRequestHeader(p, headers[p]);
            }
        }
        xhr.addEventListener('readystatechange',function(e){
            if(xhr.readyState === 4) {
                if(String(xhr.status).match(/^2\d\d$/)) {
                    resolve(xhr.responseText);
                } else {
                    reject(xhr);
                }
            }
        });
        xhr.send(data);
    })
}


function wait(duration){
    return new Promise(function(resolve, reject) {
        setTimeout(resolve,duration);
    })
}


function waitFor(element,event,useCapture){
    return new Promise(function(resolve, reject) {
        element.addEventListener(event,function listener(event){
            resolve(event)
            this.removeEventListener(event, listener, useCapture);
        },useCapture)
    })
}


function loadImage(src) {
    return new Promise(function(resolve, reject) {
        var image = new Image;
        image.addEventListener('load',function listener() {
            resolve(image);
            this.removeEventListener('load', listener, useCapture);
        });
        image.src = src;
        image.addEventListener('error',reject);
    })
}


function runScript(src) {
    return new Promise(function(resolve, reject) {
        var script = document.createElement('script');
        script.src = src;
        script.addEventListener('load',resolve);
        script.addEventListener('error',reject);
        (document.getElementsByTagName('head')[0] || document.body || document.documentElement).appendChild(script);
    })
}


function domReady() {
    return new Promise(function(resolve, reject) {
        if(document.readyState === 'complete') {
            resolve();
        } else {
            document.addEventListener('DOMContentLoaded',resolve);
        }
    })
}

兼容性垫片polyfill of the ES6 Promise

es6-promise github es6-promise npm

参考资料: