首先,要明确一点,Promise 是用来处理回调嵌套的问题,而不是用来处理回调本身的问题。
程序要素
通常,我们的程序有三个要素:
- 执行器
- 上下文
- 表达式
执行器按照语义规则在上下文中执行表达式,而表达式的最终效果都是要修改上下文,不修改上下文的表达式除了浪费时间之外,是没有意义的。 方法是表达式的集合,方法对上下文的修改可以表现在两个方面:
- 在方法体中直接修改了上下文的状态。
- 产生一个返回值,参与到接下来的表达式中。
所以,执行一个方法,最终的意义就是观察这个方法对上下文的修改结果,然后做出相应的动作。
同步与异步
同步方法的特征:
- 有直接的返回值,没有返回值的方法肯定直接修改了上下文。
- 可预期的异常栈,异常会沿着当前调用链向上传播。
异步方法的特征:
- 运行在另一个执行过程中,因此,异常不会沿着当前调用连向上传播。
- 没有直接的返回值,只能通过回调函数来观察修改结果。
对比下面的例子:
同步:
var value = fn_1();
var c = value_1 + 20;
var value_2 = fn_2(c);
console.log(value_2);
执行器 A 发起 fn_1 的调用, 执行器 A 执行并等待 fn_1 的执行完成,执行器 A 观察 fn_1 的结果。
执行器 A 发起 fn_2 的调用, 执行器 A 执行并等待 fn_2 的执行完成,执行器 A 观察 fn_2 的结果。
由于 fn_1 和 fn_2 都是同步方法,这个过程会很完美的工作。 但是,如果 fn_1 和 fn_2 都是异步方法,而异步调用根本就没有返回值,所以,这段代码是得不到预期效果的。 那我们就只能这么写了:
异步:
fn_1(function(value_1){
var c = value_1 + 20;
fn_2(c, function(value_2){
console.log(value_2);
})
})
虽然写法不一样,但是本质都是一样的,注意其中执行器的变化:
执行器 A 发起 fn_1 的调用,执行器 B 执行并等待 fn_1 的执行完成,执行器 A 观察 fn_1 的结果。
执行器 A 发起 fn_2 的调用,执行器 C 执行并等待 fn_2 的执行完成,执行器 A 观察 fn_2 的结果。
既然本质都是一样的,那么我们就可以将 “执行并等待 -> 观察”封装为一个概念——Promise,当异步方法的结果返回时,根据不同的结果,触发不同的动作。 形式如下:
var promise_1 = create_promise(fn_1);
var promise_2 = promise_1.then(function(value_1){
var c = value_1 + 20;
return create_promise(c, fn_2);
});
promise_2.then(function(value_2){
console.log(value_2);
});
如此一来,就将异步的嵌套回调,转化为扁平的同步调用形式。