Promise 就是这么简单

Promise 是干嘛的?

Promise是ES6针对js异步编程一种解决方案,也解决了ES5之前异步编程大量回调函数的写法的痛点,我们来亲切感受一下。

假设有这么一个需求:一个页面需要进行4次ajax请求才能渲染完所有内容,但是每一次请求依赖上一次请求返回的动态url。我们会联想到这将是一次链式请求。

ES5写法:

// request 假设是事先封装好的ajax方法

request(url, function(res1) {
    /* 大量业务逻辑 */
    ... 
    request(res1.url, function(res2) {
        /* 大量业务逻辑 */
        ... 
        request(res2.url, function(res3) {
            /* 大量业务逻辑 */
            ... 
            request(res3.url, function(res4) {
                /* 大量业务逻辑 */
                ... 
            });
        });
    });
});

看到这样的异步回调,是不是有一种在地狱间来回穿梭的感觉,这就是传说中的“回调地狱”,代码层级嵌套深,结构很不直观,增加大量的维护成本(当然谁会去想维护这样的代码?,何以解忧,唯有离职)-0—。

ES6写法:

// request 假设是事先封装好的ajax Promise 方法

request(url, (res) => {
    /* 大量业务逻辑 */
    ... 
    return request(res.url);
}).then((res) => {
    /* 大量业务逻辑 */
    ... 
    return request(res.url);
}).then((res) => {
    /* 大量业务逻辑 */
    ... 
    return request(res.url);
}).then((res) => {
    /* 大量业务逻辑 */
    ... 
});

领教了ES5回调地狱,回头看看这ES6的Promise写法,是不是觉得优雅不要太多,链式调用清晰明了,结构分明(当然这是我个人感觉了,你觉得呢?)-0-。

Promise 基本用法

Promise 其实就是一个构造函数,它接收一个回调函数作为一个参数,这个回调函数默认有两个参数,即resolve和reject。进行new后,返回一个promise对象,这个对象即有then,catch,finally的Promise原型方法。

1.创建一个Promise

我们先来一个最基本的promise:

// 创建一个promise对象
var promiseObj = new Promise((resolve, reject) => {
    /* 业务逻辑 */
    ... 
    if (true) {
        // success
        resolve(success);
    } else {
        reject(error);
    }
});

// 调用promise对象方法
promiseObj.then((success) => {
    // 此处为成功状态的回调,success数据即为以上resolve函数里的success
}).catch((e) => {
    // 此处为失败状态的回调, error即为以上reject函数里的error;
}).finally(() => {
    // 此处不管成功还是失败,都会执行,使用情况比较少。
});


在创建一个promise对象的时候,Promise构造函数里的参数即回调函数,它会执行一遍,所以我们通常会将new Promise的过程放在一个方法里作为return返回,在需要这个promise实例的时候,调用这个方法即可获取到这个实例并执行代码业务逻辑,具体如下。

2.封装我们想要的Promise

我们来写一个文章开头提到的Promise的request方法,深刻感受一下:

function request(url) {
    return new Promise((resolve, reject) => {
        var XHR = new XMLHttpRequest();
        XHR.open('GET', url);
        XHR.onreadystatechange = function () {
            if (XHR.readyState === 4) {
                if (XHR.status === 200) {
                    // 请求成功,将服务器返回的数据reslove出去
                    resolve(XHR.responseText);
                } else {
                    // 请求失败,将触发的错误reject出去
                    reject(new Error(XHR.responseText));
                }
            } 
        };
        XHR.send(null);
    });
}

// 调用request
request(url).then((res) => {
    // res 即为 以上resolve函数里面的XHR.responseText;
    console.log(res);
}).catch((e) => {
    // catch 通常即为捕获错误的地方,即reject返回的new Error(XHR.responseText)
    new Error(e);
});

Promise 实例方法

在Promise实例化一个promise对象后,它只有三个方法,即 then, catch, finally。

1.then

then 方法里执行的是resolve状态

request(url).then((res) => {
    // res 为resolve状态返回的数据,一般也指成功状态
})

then 方法可以链式调用,即文章开头的request方法链式then调用

request(url).then((res) => {
    ... 
    return res.data;
}).then((data) => {
    // 此处的data 即为上一步then的return值:res.data
});

所以then的return值也可以是一个promise对象:

request(url, (res) => {
    ... 
    // 返回request方法调用结果:即一个promise对象
    return request(res.url);
}).then((res) => {
    // 此处的res 即为上一步返回方法request里的的resolve值,相当于上一步返回request调用then
    ... 
    // 继续返回一个promise对象
    return request(res.url);
}).then((res) => {
    
})

2.catch

catch 方法里执行的是reject状态和错误处理

request(url).then((res) => {

}).catch((e) => {
    // reject状态或者request方法内部错误了,都会在这里被捕获到。
});

说白了,catch方法不仅处理reject状态数据,而且还会捕获因为代码运行而产生的错误,我们就叫它错误垃圾处理箱吧。

3.finally

finally 方法即不管resolve还是reject状态都会执行的方法。

request(url).then((res) => {

}).catch((e) => {

}).finally(() => {
    console.log('请求完成了');
});

Promise 静态方法

1.Promise.all()

假设一个页面有多个并行的请求,但是你想等他们都请求完后再统一获取他们返回的数据并处理,就可以用到这个方法。
此方法是将多个Promise实例包装成一个新的Promise实例。
Promise.all 方法接收一个数组作为参数,数组的值即为各个promise实例:

var promises = Promise.all([p1, p2, p3]);

promises.then((resArr) => {
    // resolve状态 此时返回的resArr为一个数组,数组值为p1,p2,p3的resolve数据
}).catch((e) => {
    // reject 状态 p1,p2,p3的reject
});

如上,Promise.all将promise实例p1,p2,p3包装成一个新的promise实例promises,此时promises的回调方法返回参数会发生一些改变:

  • promises的resolve状态由p1,p2,p3共同决定,只有p1,p2,p3状态都为resolve了,promises的状态才会变为resolve
  • promises的reject状态由p1,p2,p3任何一个决定,只要p1,p2,p3的状态某一个为reject了,promises状态就会为reject

我们可以通过多个request方法来深刻了解一下:

// requests为三个不同的请求被Promise.all包装成功一个新的promise实例
var requests = Promise.all([request(url1), request(url2), request(url3)]);

// 调用requests,会同时发出三个请求
requests.then((res) => {
    // resovle状态:当三个请求都成功以后,会进入此状态
    // 此时res 为一个数组,即三个请求返回的数据组成的数组
    console.log(res.length) // 3
}).catch((e) => {
    // reject状态: 只要三个请求其中一个发生了错误,就会进入此状态
});

2.Pormise.race()

Promise.race 同Promise.all一样,也是将多个promise实例包装成一个新的promise实例:

var promises = Pormise.race([p1, p2, p3]);

但是,它跟Promise.all不同的是,只要p1,p2,p3中谁最先变为状态(不管是resolve状态还是reject状态),promises回调函数即为它的状态。
我们先看看resolve状态:

promises.then((res) => {
    // 假设p1,p2,p3中p2最先改变为resolve状态,res参数即为p2的resolve数据
});

再看看reject状态的运用:

// 请求超时promise  设置5秒超时
function timeout () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error('timeout'));
        }, 5000);
    });
}

var requests = Promise.race([request(url), timeout()]);
requests.then((res) => {
    // 如果5秒之内成功请求,此时即为request的resolve状态
}).catch((e) => {
    // 如果5秒超时,此时即为timeout的 reject状态
});

3.Promise.resolve() 与 Promise.reject()

这两种方法都是将现有对象转为Promise对象,区别是该对象是否有then方法,有then方法则直接调用该对象的then方法,如果没有,则直接返回这个对象。
这两种方法我其实用的比较少,不过我们可以改造一下request方法来熟悉这两个方法:

// request2方法做了一些改变, 直接返回一个具有then方法和请求结果的对象。
function request2 (url) {
    var data = null;
    var XHR = new XMLHttpRequest();
    XHR.open('GET', url);
    XHR.onreadystatechange = function () {
        if (XHR.readyState === 4) {
            if (XHR.status === 200) {
                data = XHR.responseText;
            } 
        } 
    };
    XHR.send(null);

    return {
        data,
        then (resolve, reject) {
            resolve(this.data);
        }
    }; 
}

// 理由Promise.resolve 转化request2为promise对象
var promise = Promise.resolve(request2(url));
promise.then((res) => {
    // 此时 res为 request2返回对象then方法的返回值 this.data
});

Promise.reject 与之是同样的用法,但是与Promise.resolve的区别是reject状态返回的不是reject数据,而是这个request2返回的对象本身:

function request2 (url) {
    ... 
    return {
        data,
        then (resolve, reject) {
            reject('出错了');
        }
    }
}

var thenObj = request2(url);
var promise = Promise.reject(thenObj);
promise.then((res) => {

}).catch((e) => {
    // e即为thenObj对象本身,并不是 reject 数据'出错了'
    console.log(e === thenObj) //true
})

总结

通过Promise构造函数封装我们的异步业务逻辑,再进行优雅的链式then调用,跳出“回调地狱”,再在垃圾箱catch中捕获你的错误,Promise,就是这么简单。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,703评论 1 56
  • 你不知道JS:异步 第三章:Promises 在第二章,我们指出了采用回调来表达异步和管理并发时的两种主要不足:缺...
    purple_force阅读 2,058评论 0 4
  • 本文作者就是我,简书的microkof。如果您觉得本文对您的工作有意义,产生了不可估量的价值,那么请您不吝打赏我,...
    microkof阅读 15,947评论 9 40
  • 00、前言Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区...
    夜幕小草阅读 2,129评论 0 12
  • 景物细节,就是以具体细致的笔触,来描写景物,将它与众不同的特征写得生动形象,具体实在。这是一种赋予景物以生命力的最...
    实实在在教语文阅读 2,872评论 0 1