理解与使用Promise完成复杂异步处理流程

本文谈到的Promise是指javascript环境下的Promise,然而Promise这个功能在若干语言中均有实现,我本次会在Nodejs服务端环境下进行学习和理解。

Promise是为了解决日趋复杂的异步程序设计而出现的,简单的异步例如:发起一个ajax请求来获取数据,之后渲染DOM。

然而现实世界并没有这么简单,我们极有可能需要同时发起多个ajax请求并等待它们全部返回,在获得结果后可能又需要根据上一轮的数据发起下一轮的ajax获取其他数据,这样的流程完全可以演变得交织错杂,编程和维护起来是非常头疼的。

此前,我们解决这种问题就是callback的回调思路,callback嵌套callback的代码层出不穷,想追加点功能需要一层一层的数括号,这就急需一个替代方案的出现。

Promise应运而生!

Promise提供了串行/并行异步编程的简化方案,ajax-2依赖ajax-1,或者ajax3依赖ajax-1+ajax2,都可以轻松通过它轻松实现。

学习Promise的文章挺多的,我个人感觉没有说的特别明白的博客。但是,我建议你先看看这个,然后跟着我下面的例子来理解一下就行。

谈谈我对Promise的理解

Promise对象有一个状态,表示异步处理的一个进度,可以是:pending(等待结果),resolved(已完成),rejected(已失败);另外还有一个存储的区域,它用于放置异步处理完成后(resolved or rejected)的结果数据,可以是任意格式。

既然Promise本身只有这个状态而已,那么显然需要我们业务代码去驱动它的进度变迁,否则它就是一坨不会动的代码。这里,Promise的构造函数需要传入1个接受2个参数的function,我们一般是这样用的:

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

这个函数提供了2个function给业务调用,调用Resolve就可以改变这个Promise的状态为resolved,同样道理调用reject就可以让Promise的状态变为rejected。

resolve()/reject()函数接受1个入参,它会被传递给后面串联的.then()调用,这个入参可以是一个普通的对象,也可以是一个Promise对象。重要的是,如果是一个Promise对象,那么这个后面串联的then()需要等到这个Promise的状态等于终结状态(非pending)后才会被回调,而then()回调的传入值是也就是这个Promise最终resolve/reject传入的值。

有点绕吧,可以先忘记这一个段落,看看then()做了什么再回头理解。

那么,then()又是做什么的呢?

其实也很简单,如果你在上述业务逻辑里调用了resolve,那么Promise异步处理相当于终结了,then()就是指前一个Promise终结后再做什么事情的意思。

then()的函数原型也不复杂,要求我们传入一个function,当Promise通过resolve(xxx)/reject(xxx)终结后,xxx会被当做入参调用这个function,我们可以在里面做下一步的事情,从而实现串联的感觉。

值得注意的是,Promise为了实现.then()调用的串联(只有Promise对象有then方法),.then()的回调函数的返回值会被隐式的转换为Promise对象(如果你没有显式的返回Promise对象),这是then实现内部通过Promise.resolve/Promise.reject这两个API实现的,我会在后面的例子中体现这个事情。

实践出真知

为了加深理解,我亲手写了8个demo来看体验promise的各种特性,我这里逐一列出来做一个简短的说明。

如果实在理解有困难可以把代码拉到本地,使用node promise.js来运行调试一下。

// 同步resolve
var promise1 = new Promise(
    (resolve, reject) => {
        resolve("this is promise1 resolve");
    }
).then(
    (msg) => {
        console.log(msg);
    },
    (err) => {
        console.log(err);
    }
);

var promise = 这部分可以无视,我仅仅用于代码里标记一下demo的次序。这个例子体现了最基础用法,给resolve传入一个字符串终结当前的Promise的状态,因为Promise被终结,因此该字符串会被回调给then中的(msg) => {...}函数,从而实现串联。

// 同步reject
var promise2 = new Promise(
    (resolve, reject) => {
        reject("this is promise2 reject");
    }
).then(
    (msg) => {
        console.log(msg);
    },
    (err) => {
        console.log(err);
    }
);

和上个例子差不多,只是调用了reject,这样会回调(err) => {....}。

// 同步catch
var promise3 = new Promise(
    (resolve, reject) => {
        reject("this is promise3 reject catch");
    }
).then(
    (msg) => {
        console.log(msg);
    }
).catch(
    (err) => {
        console.log(err);
    }
);

如果我没有在then()里提供reject的回调函数,那么这个reject事件会继续向后移动,直到遇到catch会被处理。

// 异步resolve
var promise4 = new Promise(
    (resolve, reject) => {
        var promise4_1 = new Promise(
            (resolve, reject) => {
                console.log("promise4_1 starts");
                setTimeout(
                    () => {
                        resolve("this is promise4_1 resolve");
                    },
                    2000
                );
            }
        );
        resolve(promise4_1);
    }
).then(
    (msg) => {
        console.log(msg);
    },
    (err) => {
        console.log(err);
    }
);

这里,我故意营造了一个resolve(Promise Object)的例子(也就是promise4_1),这样的话then()会等到这个Promise Object自身的异步流程处理结束后再回调,这相当于为promise4异步流程节外生枝了promise4_1,等枝叶长成后再回到promise4主干继续向后链式处理。

// 链式resolve
var promise5 = new Promise(
    (resolve, reject) => {
        var promise4_1 = new Promise(
            (resolve, reject) => {
                console.log("promise5_1 starts");
                setTimeout(
                    () => {
                        resolve("this is promise5_1 resolve");
                    },
                    2000
                );
            }
        );
        resolve(promise4_1);
    }
).then(
    (msg) => {
        console.log(msg);
        var promise5_2 =  new Promise(
            (resolve, reject) => {
                console.log("promise5_2 starts");
                setTimeout(
                    () => {
                        resolve("this is promise5_2 resolve");
                    },
                    2000
                );
            }
        );
        return promise5_2;
    }
).then(
    (msg) => {
        console.log(msg);
        throw new Error();
    }
).catch(
    () => {
        console.log("exception catched after promise5_2 resolved");
    }
);

这个例子变得再复杂一些,除了在promise5中节外生枝promise5_1异步处理2秒,在2秒后回到主干后的.then()环节,我通过return返回一个Promise对象再次节外生枝promise5_2异步执行2秒,之后再次回到主干的.then()打印出消息并且抛出了异常,最终由catch捕获。

// 并行+链式promise
var promise6 = new Promise(
    (resolve, reject) => {
        var promiseArr = [];
        for (var i = 0; i < 5; ++i) {
            promiseArr.push(new Promise(
                (resolve, reject) => {
                    console.log(`promise6_${i} starts`);
                    ((index) => { // 闭包处理i
                        setTimeout(
                            () => {
                                console.log(`before promise6_${index} resolved`);
                                resolve(`this is promise6_${index} resolve`);
                            },
                            index * 1000
                        );
                    })(i);
                }
            ));
        }
        resolve(Promise.all(promiseArr));
    }
).then(
    (msgArr) => {
        console.log(`promise6 all resolved ${msgArr}`);
    }
);

这个例子主要是体验Promise.all(),这个函数其实创建返回了一个Promise对象,内部管理与并发了多个Promise流程(节外生枝了N个树叉),它等待它们全部完成或者任意失败之后会终结自己,在外层通过resolve将Promise.all()返回的集合式Promise对象串联(托管)起来,最终进入下一个then从而可以访问N个树叉的结果集合。

// .then()隐式包装resolved Promise
var promise7 = new Promise(
    (resolve, reject) => {
        var promise7_1 = new Promise(
            (resolve, reject) => {
                console.log("promise7_1 starts");
                setTimeout(
                    () => {
                        resolve("this is promise7_1 resolve");
                    },
                    2000
                );
            }
        );
        resolve(promise7_1);
    }
).then(
    (msg) => {
        console.log(msg);
        return "promise7 .then()隐式包装resolved Promise";
    },
    (err) => {
        console.log(err);
    }
).then(
    (word) => {
        console.log(word);
    }
);

这个例子除了节外生枝外,主要关注在于第1个.then()中return了一个字符串,它实际被隐式的包装成了一个resolved状态的Promise对象返回(这是我想强调的重点),从而继续链式的调用第2个.then()的(word) => {...}回调函数。

// .then()显式包装resolved Promise
var promise8 = new Promise(
    (resolve, reject) => {
        var promise8_1 = new Promise(
            (resolve, reject) => {
                console.log("promise8_1 starts");
                setTimeout(
                    () => {
                        resolve("this is promise8_1 resolve");
                    },
                    2000
                );
            }
        );
        resolve(promise8_1);
    }
).then(
    (msg) => {
        console.log(msg);
        return Promise.resolve("promise8 .then()显式包装resolved Promise");
    },
    (err) => {
        console.log(err);
    }
).then(
    (word) => {
        console.log(word);
    }
);

这个例子和上一个例子等价,这里体现了第1个.then()显式调用Promise.resolve返回一个Promise对象,从而第2个.then()回调(word) => {}。

// .then()显式包装rejected Promise
var promise9 = new Promise(
    (resolve, reject) => {
        var promise9_1 = new Promise(
            (resolve, reject) => {
                console.log("promise9_1 starts");
                setTimeout(
                    () => {
                        resolve("this is promise9_1 resolve");
                    },
                    2000
                );
            }
        );
        resolve(promise9_1);
    }
).then(
    (msg) => {
        console.log(msg);
        return Promise.reject("promise9 .then()显式包装rejected Promise");
    },
    (err) => {
        console.log(err);
    }
).catch(
    (word) => {
        console.log(word);
    }
);

这个例子和上面2个例子相反,我在第1个.then()显式的返回了一个rejected的Promise对象,这是通过Promise.reject包装字符串而成的,因此catch将被调用。

通过最后3个例子,我们应该可以明确的感受到Promise围绕pending,resolved,rejected三个状态实现的异步状态驱动以及串联/并行调用的触发动机与原理。

关于Promise本身的功能就了解这么多,希望后面有机会在react下多多使用,解决一些并发ajax以及串联ajax的异步需求,关键还是找到应用场景进行合理的套用,这是我认为最难的地方。

另外,需要记住Promise是ES6的产物,而未来ES7提出了async/await关键字将对Promise加以利用进一步简化异步编程,它将更接近于协程的理念,更加符合人类的思考习惯,至少我是这么认为的。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值