Promise异步处理

Promise的状态

Promise是异步编程的解决方案之一,相比传统的回调(解决回调地狱)和事件机制更为合理和强大。
Promise实例有三种状态:

  • Pending:进行中
  • Resolved:已完成
  • Rejected:已失败

Promise实例的状态只能由 Pending->Resolved, Pending->Rejected。一旦Promise实例的状态发生改变,就不能被更改。

状态不可控

一旦创建Promise,便会立刻执行,无法取消。
处于pending状态时,无法得知进程具体的信息,比如完成百分比(虽然可以自行设置回调进行通知)。

失败的状态

成功的状态只能由resolve方法转成。
失败的状态可以由reject方法转成,也可以由抛出错误间接转成。

1
2
3
4
5
6
7
8
9
10
11
12
13
三者都会正常的打印出失败的信息。

new Promise((resolve, reject) => {
reject('error');
}).catch(console.log); // error

new Promise((resolve, reject) => {
a;
}).catch(console.log); // ReferenceError: a is not defined

new Promise((resolve, reject) => {
throw 'error';
}).catch(console.log); // Error: error

错误的报告机制

如果失败状态没有接收失败的回调函数接收,Promise会抛出错误。
这里的抛出错误,仅仅是在控制台显示之类的提示,不会终止程序的进程。

1
2
3
4
5
6
7
8
9
先打印出 'err' ,再报错。

new Promise((resolve, reject) => {
reject();
});

new Promise((resolve, reject) => {
reject('err');
}).then(() => {}, console.log);

一旦Promise设置了失败回调函数,即便是代码执行错误,也会自行消化,不外报。

1
2
3
4
5
虽然 a 未被定义,但全程安静,无槽点。

new Promise((resolve, reject) => {
a;
}).then(() => {}, () => {});

Promise构造函数

Promise是一个构造函数,用来生成Promise实例

1
2
3
4
5
6
7
8
var promise = new Promise(function(resolve, reject)){

if (/*异步操作成果*/) {
resolve(value)
} else { /*异步操作失败*/
reject(error)
}
}

执行顺序

传入方法
创建Promise的同时也会执行传入方法。
传入方法不会因为调用了resolve/reject便终止执行,所以更优的方式是retrun resolve/reject。

1
2
3
4
5
6
7
打印出 1 2 

new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
});

回调方法
立即得到结果的Promise,其回调函数依然会晚于本轮事件执行。
这种后执行不同于setTimeout的将执行函数push到执行栈,而是将执行函数放到本轮的末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
得到的结果是:1 2 3 4 5 6 

console.log(1);

setTimeout(() => {
console.log(5);
});

let p = new Promise((resolve, reject) => {
console.log(2);
resolve();
});


p.then(function() {
console.log(4);
});

setTimeout(() => {
console.log(6);
});

console.log(3);

执行顺序涉及到JS的运行机制(事件循环)
macro-task:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task:process.nextTick, Promises, Object.observe, MutationObserver
执行顺序:函数调用栈清空只剩全局执行上下文,然后开始执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次执行macro-task中的一个任务队列,执行完之后再执行所有的micro-task,就这样一直循环。

事件循环机制呢,简单点来说,就是在执行上下文的过程中,对函数的入栈和出栈。执行前函数先入栈,执行完后函数出栈。如若遇到了一些异步操作像回调函数以及ajax、setTimeout,Promise等,会先将他们交给浏览器的其他模块去执行,执行完后,会把回调函数放入到taskqueue中。当所有的call stack执行完后再开始执行task queue中的函数。

我们知道,Promise的回调函数不是传入的,而是使用then来调用的。因此,Promise中定义的函数应该是马上执行的,then才是其回调函数,放入queue队列中。

具体的过程可以看上面那篇文章。大概过程如下:

  1. 主线程同步任务console.log(1);直接打印 1
  2. 遇到setTimeout,交给其他模块执行,执行完后回调放入macro-task中
  3. 遇到Promise,立即执行里面的function,打印 2。
  4. 循环开始,遇到resolve(),修改Promise状态为fulfill。继续执行。
  5. 遇到then,将回调放入micro-task中。
  6. 遇到setTimeout,交给其他模块执行,执行完后回调放入macro-task中
  7. 主线程同步任务console.log(3);直接打印 3
  8. call stack执行完毕了。开始执行micro-task中的回调函数,输出 4。
  9. micro-task执行完毕,开始执行macro-task中的回调函数,分别输出5,6。
  10. 结束(输出结果是:1 2 3 4 5 6)

结果参数

传入reject的参数,一般是字符串或Error实例,表示抛出的错误。
传入resolve的参数,一般是相应的JSON数据等,表示得到的数据。

传入resolve的参数,还可以是另一个Promise实例。
这时,只有当内层的Promise结束后,外层的Promise才会结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
过两秒后,打印出 2000 

new Promise((resolve, reject) => {
resolve(createPromise());
}).then(console.log);

function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2000);
}, 2000);
});
}

在这种情况下,如果内层失败,并不等于传递Error实例给resolve不同。
前者是内层Promise抛出了错误将被外层捕获,后者仅仅是参数为一个Error实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
内层失败的信息,被外层捕获。过两秒,打印出 '2' 2000 

new Promise((resolve, reject) => {
resolve(createPromise());
}).then(res => {
console.log('1', res);
}, err => {
console.log('2', err);
});

function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(2000);
}, 2000);
});
}

实例方法

then()

该方法可传入两个,分别对应成功/失败时的回调函数。
该方法返回的是一个新的Promise对象,这也是可以使用链式(.then.then…)的原因。

1
2
3
let p1 = new Promise(resolve => resolve(2000));
let p2 = p1.then(() => {}, () => {});
console.log(p1 === p2); // false

return
链式中,后者的状态取决于前者(成功/失败)的回调函数中返回(return)的结果。
如果没有返回,相当返回一个成功的状态,值为undefined。
如果返回为Promise对象,后者的状态由该对象的最终状态决定。
如果返回为非Promise对象的数据,相当返回一个成功的状态,值为此数据。
如果前者执行时抛出了错误,相当是返回一个失败的状态,值为此错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
依次打印出:
1 res 2000
2 res undefined
3 res 3000
4 err 4000

new Promise(resolve => resolve(2000))
.then(res => {
console.log('1 res', res);
})
.then(res => {
console.log('2 res', res);
return 3000;
})
.then(res => {
console.log('3 res', res);
return new Promise((resolve, reject) => {
reject(4000);
});
})
.then(console.log, err => {
console.log('4 err', err);
});

状态的传递
在链式中,如果前者的状态没有被后者捕获,会一直冒泡到被捕获为止。
状态被捕获后便消失,这之后的的状态由当前then返回的状态决定,之后重复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
依次打印出:
2 res 2000
3 res 3000

new Promise(resolve => resolve(2000))
.then(null, err => {
console.log('1 err', err);
})
.then(res => {
console.log('2 res', res);
return 3000;
})
.then(res => {
console.log('3 res', res);
});

catch()

用于指定发生错误时的回调函数,等价于:.then(null, callback)。
其表现与then一致,比如返回新的Promise,状态的继承和传递等等。

一般推荐使用catch而不是then的第二个方法接收错误。
因为catch可以捕获then自身的错误,也更接近同步的写法(try/catch)。

1
2
3
4
5
6
7
new Promise(() => {})
.then(() => {
...
})
.catch(() => {
...
});

finally()

用于Promise处理结束后的收尾工作。
传入其的回调函数不会接受任何参数,意味着没有办法知道Promise的结果。
这也正表明,finally里面的操作与状态无关,不依赖Promise的处理结果。

其本质和catch一样,也是then方法的变种。
不过其仅仅是状态的传递者,只会返回原状态,不会接收状态和创建新的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
p.finally(() => {
// codes...
});

--- 等价于

p.then(res => {
// codes...
return res; // 将原成功状态返回
}, err => {
// codes...
throw err; // 将原失败状态返回
});

e.g. 在请求数据时,我们会显示加载图案,请求完成后无论结果都要隐藏此图案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一般,一个完整的 Promise 的结构会如下。

showLoading = true;

new Promise((resolve, reject) => {
// 请求...
})
.then(res => {
// 成功处理...
})
.catch(err => {
// 失败处理...
})
.finally(() => {
// 重置一些状态...
showLoading = false;
});

静态方法

resolve()

此方法直接返回一个状态为resolved,值为其参数的Promise。

1
2
3
Promise.resolve(res);
--- 等价于
new Promise(resolve => resolve(res));

reject()

此方法直接返回一个状态为rejected,值为其参数的Promise。

1
2
3
Promise.reject(res);
--- 等价于
new Promise((resolve, reject) => reject(res));

all()

此方法用于将多个Promise实例,包装成一个新的Promise实例。
其参数为一个数组,每一项应为Promise实例(不是则会使用Promise.resolve进行转化)。

新Promise的状态取决于传入数组中的每一项的最终状态。
如果有一项状态变成rejected,新实例则为rejected,值为该项的返回值。
如果全部项都变成了resolved,新实例则为resolved,值为包含每一项返回值的数组。

1
2
3
4
5
6
7
8
9
10
11
三秒后,打印出:[1, 2, 3]。

let pArr = [1, 2, 3].map(createPromise);

Promise.all(pArr).then(console.log);

function (num) {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(num) }, num * 1000);
});
}

race()

此方法与all()基本相同,传入的参数也是一个Promise数组。 (取决于第一个状态)
不同的是,新Promise的最终状态是由数组中第一个状态改变的项(成功或失败)决定的。

1
2
3
4
5
6
7
8
9
10
11
结果:Error: time out  
var p = Promise.race([
new Promise(resolve => {
setTimeout(() => resolve('p1'), 10000);
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('time out')), 10);
})
]);
p.then( ret => console.log(ret))
.catch( err => console.log(err.toString()));

原文:大专栏  Promise异步处理


猜你喜欢

转载自www.cnblogs.com/petewell/p/11612103.html