View on GitHub

Yinjie - GitHub.io

Welcome to the Yinjie's notes

promise 由浅入深

首先说一下 “由浅入深” 这个词用的过了,一般说到这个词就已经是可以出书的概念了。 这里的 “由浅入深” 只是一个意愿,也是从出了这个特性就一直用到现在的一些个人小记录。会用的挑着看,不会用的从头看。

背景由来

要说到 JS 和其它语言相比,直观上最“大”(*注1)的特征,也就算是异步回调了。 但是这种回调在编程写法上很不友好,一两个回调还好,一但多起来、尤其是相互套用,这就很“难看”了,也出现了所谓的 “callback hell” 回调地狱。 这种情况对一些逻辑实现和模块设计上都带来一些不便。

这个时候,就出现了一些解决此类问题的方法,到现在,成为公认解决方案的就是 promise 规范。

基础解释

promise 是用一种链式调用的方式,从形式上解决了回调嵌套调用的问题。当然,这只是形式上,因为是 JS 运行机制的问题,无法从根本上解决。

看下面的例子:

/**
 * 根据用户ID获取用户信息
 * @param uid 用户id
 * @callback 回调方法
 */
function (uid, callback) {
    $.ajax({
        url: '/user/info',
        data: {uid: uid},
        dataType: 'json',
        success: function (data) {
            callback({
                data: data,
                status: 'success',
                message: ''
            });
        },
        error: function (err) {
            callback({
                data: null,
                status: 'success',
                message: err.state
            });
        }
    });
}

这是一个很典型的异步请求回调的方法,因为 request 请求是异步的,方法执行完不能像同步执行那样直接 return 一个数据,所以一般只能是再传入一个回调方法作为参数来接收处理数据。

这样在方法、模块拆分上就很不直观了。这个时候,promise 就出现了,它可以从代码字面上去掉回调方法的参数,返回『结果数据』。

/**
 * 根据用户ID获取用户信息
 * @param uid 用户id
 * @return 用户数据
 */
function getUserinfo(uid) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: '/user/info',
            data: {uid: uid},
            dataType: 'json',
            success: function (data) {
                resolve({
                    data: data,
                    status: 'success',
                    message: ''
                });
            },
            error: function (err) {
                reject({
                    data: null,
                    status: 'success',
                    message: err.state
                });
            }
        });
    });
}

这个终于有 return 了,从代码易读性和维护性上已经有很大程序的提高,虽然它并没有实际解决回调问题,看用法:。

var userinfo = getUserinfo('U123321');
userinfo.then((data) => {
    /*
    {
        data: data,
        status: 'success',
        message: ''
    }
     */
}).catch((err) => {
    /*
    {
        data: null,
        status: 'success',
        message: err.state
    }
     */
});

promise 深入用法

Promise.all

有时我们会遇到这种需求:在多个异步请求成功之后,再执行某个操作。比如批量单个上传几张图片后,给予用户一个成功的反馈。 这种需求通过传统的异步回调写地尤为麻烦,在某些情况下甚至是个不能完成的逻辑。 promise 自然也想到了这一点,对多个异步回调后的执行操作专门定了一个 all 方法。

Promise.all 是个静态方法,可以直接调用,唯一参数是一个数组。数组元素各是一个标准的 Promise 实例。如下

const p1 = new Promise(...);
const p2 = new Promise(...);
const p3 = new Promise(...);
const p4 = new Promise(...);

Promise.all([p1, p2, p3, p4])
    .then((result) => {
        // 所有异步过程都执行成功 (resolve)
    })
    .catch((err) => {
        // 某一异步过程执行失败 (reject)
    });

要注意的是,Promise.allthencatch 的判定情况:

  • then: 所有前置异步过程都执行成功,then 返回的数据就是所有异步过程返回的数据数组
  • catch: 一个前置异步过程失败就是此失败时的 reject 信息

Promise.race

和以上情况一样,也会遇到这类需求:某几个异步过程,其中有一个完成后,就进行下一步动作。就跟逻辑的 andor 一样,Promise.all 就是 and,Promise.race 就是 or 的特征,也就算是异步回调了。

const p1 = new Promise(...);
const p2 = new Promise(...);
const p3 = new Promise(...);
const p4 = new Promise(...);

Promise.race([p1, p2, p3, p4])
    .then((result) => {
        // 第一个异步过程成功后的 resolve
    })
    .catch((err) => {
        // 第一个异步过程成功后的 reject
    });

race 从字面上已经很形象了 — 赛跑。就是谁是第一个,不管成功还是失败,就按它说的算。

Promise 是一个很好的异步解决方案,在平常的接口开发中可以适当去用,让自己的逻辑更清晰。

注1:这种特性现在已经不存在了,基本上现有成熟的语言都有了自己的异步执行方式和“事件”回调


下一篇简要说一下 async/await 的异步解决方案。