Promise 与事件监听类似,除了以下几点:

  1. 一个 Promise 只能成功或者失败一次。它不能进行从成功到失败的状态转换,反之亦然。
  2. 如果在一个 Promise 成功或者失败后添加了相应的回调函数,那么该回调还是会被立即执行,尽管相关事件早已发生。

Promise 术语

一个 Promise 处于以下四种状态之一。

  • fulfilled:操作成功。
  • rejected:操作失败。
  • pending:操作进行中,还没有得到操作结果。
  • settled:Promise 已经被 fulfilled 或 rejected,且不是 pending。

JavaScript 中的 Promise

Promise 的构造器接受一个函数作为回调函数,同时给回调函数提供了两个参数:resolve 和 reject。

var promise = new Promise(function(resolve, reject) {
    // 类似异步操作的一些逻辑,然后...
    
    if(/*正常直行*/) {
        resolve('Stuff worked!);
    } else {
        reject(Error('It broke'));
    }
});

下面看下如何使用 Promise:

promise.then(function(result) {
    console.log(result); // 执行成功
}, function(err) {
    console.log(err); // Error: '执行失败'
});

与其它库的兼容性

jQuery 中的 Deferred 对象与 Promise/A+ 并不兼容。jQuery 中也有 Promise 类型,但它只是 Deferred 的一个子集。我们可以将它们转换成标准的 Promise,而且最好当它们一出现的时候就进行转换:

var jsPromise = Promise.reslove($.ajax('/whatever.json'));

// jQuery 的 Deferred 的 then 返回了更多的参数
var jqDeferred = $.ajax('/whatever.json');
jqDeferred.then(function(response, statusText, xhrObj) {
    // ...
}, function(xhrObj, textStatus, err) {
    // ...
});

// 转化为 Promise 后只会保留第一个参数,其它参数均会被忽略
jsPromise.then(function(response) {
    // ...
}, function(xhrObj) {
    // ...
});

将 Promise 用于 XMLHttpRequest

// 用 Promise 封装 XMLHttpRequest 实现的简单 Get 方法
function get(url) {
    return new Promise(function(reslove, reject) {
        var req = new XMLHttpRequest();
        req.open('GET', url);
        
        req.onload = function() {
            if (req.status == 200) {
                resolve(req.response);
            } else {
                reject(Error(req.statusText));
            }
        };
        
        req.onerror = function() {
            reject(Error('Network Error'));
        };
        
        req.send();
    });
}

下面是使用方法:

get('story.json').then(function(response) {
    
}, function(error) {
    
});

链式调用

var promise = new Promise(function(resolve, reject) {
    resolve(1);
});

promise.then(function(val) {
    console.log(val);
    return val + 2;
}).then(function(val) {
    console.log(val);
});

// 可以使用这种方式修改之前的代码
get('story.json').then(function(response) {
    return JSON.parse(response);
}).then(function(response) {
    console.log(response);
});
// 再简化
get('story.json').then(JSON.parse).then(function(response) {
    console.log(response); // json 格式的数据
});
// 更好的方式
function getJSON(url) {
    return get(url).then(JSON.parse);
}

异步操作队列

当在“then”方法的回调函数里有返回值的时候,这里有些重要的事情需要我们关注。如果你返回的是一个值,那么下一个“then”会立即调用,并把这个值传递给它的回调函数。如果你返回的是一个“类 Promise”对象,那么下一个“then”方法会等待执行,直到这个 Promise 被解决(成功或失败)后才执行。

getJSON('story.json').then(function(story) {
    return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
    console.log(chapter1);
});

现在同简单的回调模式相比,Promise 开始显示出自己的优势了。可以像下面这样写个获取章节内容的函数:

var storyPromise;

function getChapter(i) {
    // 闭包
    storyPromise = storyPromise || getJSON('story.json');
    return storyPromise.then(function(story) {
        return getJSON(story.chapterUrls[i]);
    });
}

getChapter(0).then(function(chapter) {
    console.log(chapter);
    return getChapter(1);
}).then(function(chapter) {
    console.log(chapter);
});

错误处理

// 注意 then(func1, func2) 和 then(func1).catch(func2) 的区别
// catch 类似于 try/catch ,能够捕获到上一步中的任何异常,其后的
// then 还可以继续执行
asyncThing1().then(function(){
    return asyncThing2();
}).then(function(){
    return asyncThing3();
}).catch(function(err){
    return asyncRecovery1();
}).then(function(){
    return asyncThing4();
}, function(err){
    return asyncRecovery2();
}).catch(function(err){
    console.log("Don't worry about it.");
}).then(function(){
    console.log('All done!');
});

并行和串行

这里是并行和串行的异步操作同时存在的例子:

var sequence = Promise.resolve();

story.chapterUrls.forEach(function(chapterUrl){
    // 在 sequence 后添加如下操作
    sequence = sequence.then(function() {
        return getJSON(chapterUrl);
    }).then(function(chapter){
        addHtmlToPage(chapter.html);
    });
});

Promise.resolve() 会根据传入的参数返回一个 Promise。与之对应的 Promise.reject(val) 方法,会返回一个根据你传入的参数值(或 underfined)为 reject 结果的 Promise。

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return story.chapterUrls.reduce(function(sequence, chapterUrl) {
        // 当前一章节的 Promise 完成后
        return sequence.then(function(){
            // 接着获取下一章节
            return getJSON(chapterUrl);
        }).then(function(chapter){
            addHtmlToPage(chapter.html);
        });
    }, Promise.reslove());
}).then(function(){
    addTextToPage("All done.");
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

Promise.all 接受一个 Promise 数组作为参数,创建一个所有的 Promise 都 resolve 后才 resolve 的 Promise 的对象,resolve 结果是一个数组,包含了前面所有 Promise 的 resolve 结果,并且顺序和传入时的顺序是完全一致的。

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return Promise.all(
        story.chapterUrls.map(getJSON)
    );
}).then(function(chapters){
    chapters.forEach(function(chapter) {
        addHtmlToPage(chapter.html);
    });
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

不想等到所有章节都获取到后才显示到页面上:

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return story.chapterUrls.map(getJSON) // getJSON 后的 Promise 
        .reduce(function(sequence, chapterPromise){
            return sequence.then(function(){
                return chapterPromise;
            }).then(function(chapter){
                addHtmlToPage(chapter.html);
            });
        }, Promise.resolve());
}).then(function(){
    addTextToPage("All done.");
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

Promise 厉害的地方还远不止于此!