1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-27 20:32:21 +00:00

edit generator && iterator

This commit is contained in:
Ruan Yifeng 2015-07-07 08:05:42 +08:00
parent 508fc8d624
commit 21e842bde6
3 changed files with 93 additions and 85 deletions

View File

@ -2,9 +2,13 @@
## 简介
所谓Generator有多种理解角度。首先可以把它理解成一个函数的内部状态的遍历器每调用一次函数的内部状态发生一次改变可以理解成发生某些事件。ES6引入Generator函数作用就是可以完全控制函数的内部状态的变化依次遍历这些状态。
### 基本概念
在形式上Generator是一个普通函数但是有两个特征。一是function命令与函数名之间有一个星号二是函数体内部使用yield语句定义遍历器的每个成员即不同的内部状态yield语句在英语里的意思就是“产出”
Generator函数是ES6提供的一种异步编程解决方案语法行为与传统函数完全不同。本章详细介绍Generator函数的语法和API它的异步编程应用请看《异步操作》一章。
Generator函数有多种理解角度。从语法上首先可以把它理解成一个函数的内部状态的遍历器也就是说Generator函数是一个状态机。它每调用一次就进入下一个内部状态。Generator函数可以控制内部状态的变化依次遍历这些状态。
在形式上Generator函数是一个普通函数但是有两个特征。一是function命令与函数名之间有一个星号二是函数体内部使用yield语句定义遍历器的每个成员即不同的内部状态yield语句在英语里的意思就是“产出”
```javascript
function* helloWorldGenerator() {
@ -16,12 +20,13 @@ function* helloWorldGenerator() {
var hw = helloWorldGenerator();
```
上面代码定义了一个Generator函数helloWorldGenerator的遍历器有两个成员“hello”和“world”。调用这个函数就会得到遍历器
上面代码定义了一个Generator函数helloWorldGenerator内部有两个yield语句“hello”和“world”即该函数有三个状态helloworld和return语句结束执行
当调用Generator函数的时候该函数并不执行而是返回一个遍历器可以理解成暂停执行。以后每次调用这个遍历器的next方法就从函数体的头部或者上一次停下来的地方开始执行可以理解成恢复执行直到遇到下一个yield语句为止。也就是说next方法就是在遍历yield语句定义的内部状态。
然后Generator函数的调用方法与普通函数一样也是在函数名后面加上一对圆括号。不同的是调用Generator函数后该函数并不执行返回的也不是函数运行结果而是一个指向内部状态的指针对象也就是上一章介绍的遍历器对象Iterator Object
下一步必须调用遍历器对象的next方法使得指针移向下一个状态。也就是说每次调用next方法内部指针就从函数头部或上一次停下来的地方开始执行直到遇到下一个yield语句或return语句为止。换言之Generator函数是分段执行的yield命令是暂停执行的标记而next方法可以恢复执行。
```javascript
hw.next()
// { value: 'hello', done: false }
@ -33,46 +38,49 @@ hw.next()
hw.next()
// { value: undefined, done: true }
```
上面代码一共调用了四次next方法。
第一次调用,函数开始执行,直到遇到第一句yield语句为止。next方法返回一个对象它的value属性就是当前yield语句的值hellodone属性的值false表示遍历还没有结束。
第一次调用,Generator函数开始执行直到遇到第一个yield语句为止。next方法返回一个对象它的value属性就是当前yield语句的值hellodone属性的值false表示遍历还没有结束。
第二次调用函数从上次yield语句停下的地方一直执行到下一个yield语句。next方法返回的对象的value属性就是当前yield语句的值worlddone属性的值false表示遍历还没有结束。
第二次调用,Generator函数从上次yield语句停下的地方一直执行到下一个yield语句。next方法返回的对象的value属性就是当前yield语句的值worlddone属性的值false表示遍历还没有结束。
第三次调用函数从上次yield语句停下的地方一直执行到return语句如果没有return语句就执行到函数结束。next方法返回的对象的value属性就是紧跟在return语句后面的表达式的值如果没有return语句则value属性的值为undefineddone属性的值true表示遍历已经结束。
第三次调用,Generator函数从上次yield语句停下的地方一直执行到return语句如果没有return语句就执行到函数结束。next方法返回的对象的value属性就是紧跟在return语句后面的表达式的值如果没有return语句则value属性的值为undefineddone属性的值true表示遍历已经结束。
第四次调用此时函数已经运行完毕next方法返回对象的value属性为undefineddone属性为true。以后再调用next方法返回的都是这个值。
第四次调用,此时Generator函数已经运行完毕next方法返回对象的value属性为undefineddone属性为true。以后再调用next方法返回的都是这个值。
总结一下,Generator函数使用iterator接口每次调用next方法的返回值就是一个标准的iterator返回值有着value和done两个属性的对象。其中value是yield语句后面那个表达式的值done是一个布尔值,表示是否遍历结束。
总结一下,调用Generator函数返回一个部署了Iterator接口的遍历器对象用来操作内部指针。以后每次调用遍历器对象的next方法就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值是yield语句后面那个表达式的值done属性是一个布尔值,表示是否遍历结束。
上一章说过任意一个对象的Symbol.iterator属性等于该对象的遍历器函数即调用该函数会返回该对象的一个遍历器。遍历器本身也是一个对象它的Symbol.iterator属性执行后返回自身。
### yield语句
由于Generator函数返回的遍历器只有调用next方法才会遍历下一个内部状态所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。
遍历器next方法的运行逻辑如下。
1遇到yield语句就暂停执行后面的操作并将紧跟在yield后面的那个表达式的值作为返回的对象的value属性值。
2下一次调用next方法时再继续往下执行直到遇到下一个yield语句。
3如果没有再遇到新的yield语句就一直运行到函数结束直到return语句为止并将return语句后面的表达式的值作为返回的对象的value属性值。
4如果该函数没有return语句则返回的对象的value属性值为undefined。
需要注意的是yield语句后面的表达式只有当调用next方法、内部指针指向该语句时才会执行因此等于为JavaScript提供了手动的“惰性求值”Lazy Evaluation的语法功能。
```javascript
function* gen(){
// some code
function* gen{
yield 123 + 456;
}
var g = gen();
g[Symbol.iterator]() === g
// true
```
上面代码中,gen是一个Generator函数调用它会生成一个遍历器g。遍历器g的Symbol.iterator属性是一个遍历器函数执行后返回它自己
上面代码中yield后面的表达式`123 + 456`不会立即求值只会在next方法将指针移到这一句时才会求值。
由于Generator函数返回的遍历器只有调用next方法才会遍历下一个成员所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志next方法遇到yield就会暂停执行后面的操作并将紧跟在yield后面的那个表达式的值作为返回对象的value属性的值。当下一次调用next方法时再继续往下执行直到遇到下一个yield语句。如果没有再遇到新的yield语句就一直运行到函数结束将return语句后面的表达式的值作为value属性的值如果该函数没有return语句则value属性的值为undefined。另一方面由于yield后面的表达式直到调用next方法时才会执行因此等于为JavaScript提供了手动的“惰性求值”Lazy Evaluation的语法功能。
yield语句与return语句有点像都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield函数暂停执行下一次再从该位置继续向后执行而return语句不具备位置记忆的功能。一个函数里面只能执行一次或者说一个return语句但是可以执行多次或者说多个yield语句。正常函数只能返回一个值因为只能执行一次returnGenerator函数可以返回一系列的值因为可以有任意多个yield。从另一个角度看也可以说Generator生成了一系列的值这也就是它的名称的来历在英语中generator这个词是“生成器”的意思
yield语句与return语句既有相似之处也有区别。相似之处在于都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield函数暂停执行下一次再从该位置继续向后执行而return语句不具备位置记忆的功能。一个函数里面只能执行一次或者说一个return语句但是可以执行多次或者说多个yield语句。正常函数只能返回一个值因为只能执行一次returnGenerator函数可以返回一系列的值因为可以有任意多个yield。从另一个角度看也可以说Generator生成了一系列的值这也就是它的名称的来历在英语中generator这个词是“生成器”的意思
Generator函数可以不用yield语句这时就变成了一个单纯的暂缓执行函数。
```javascript
function* f() {
console.log('执行了!')
}
@ -82,7 +90,6 @@ var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
```
上面代码中函数f如果是普通函数在为变量generator赋值时就会执行。但是函数f是一个Generator函数就变成只有调用next方法时函数f才会执行。
@ -90,12 +97,10 @@ setTimeout(function () {
另外需要注意yield语句不能用在普通函数中否则会报错。
```javascript
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
```
上面代码在一个普通函数中使用yield语句结果产生一个句法错误。
@ -103,7 +108,6 @@ setTimeout(function () {
下面是另外一个例子。
```javascript
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a){
@ -119,7 +123,6 @@ var flat = function* (a){
for (var f of flat(arr)){
console.log(f);
}
```
上面代码也会产生句法错误因为forEach方法的参数是一个普通函数但是在里面使用了yield语句。一种修改方法是改用for循环。
@ -145,6 +148,25 @@ for (var f of flat(arr)){
// 1, 2, 3, 4, 5, 6
```
### 与Iterator的关系
上一章说过任意一个对象的Symbol.iterator属性等于该对象的遍历器函数调用该函数会返回该对象的一个遍历器。
遍历器本身也是一个对象它的Symbol.iterator属性执行后返回自身。
```javascript
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
```
上面代码中gen是一个Generator函数调用它会生成一个遍历器g。遍历器g的Symbol.iterator属性是一个遍历器函数执行后返回它自己。
## next方法的参数
yield语句本身没有返回值或者说总是返回undefined。next方法可以带一个参数该参数就会被当作上一个yield语句的返回值。

View File

@ -1,19 +1,28 @@
# Iterator和for...of循环
## Iterator遍历器
## Iterator遍历器的概念
### 概念
JavaScript原有的表示“集合”的数据结构主要是数组Array和对象ObjectES6又添加了Map和Set。这样就有了四种数据集合用户还可以组合使用它们定义自己的数据结构比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制来处理所有不同的数据结构。
JavaScript原有的数据结构主要是数组Array和对象ObjectES6又添加了Map和Set用户还可以组合使用它们定义自己的数据结构。这就需要一种统一的接口机制来处理所有不同的数据结构
遍历器Iterator就是这样一种机制。它是一种接口为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口就可以完成遍历操作即依次处理该数据结构的所有成员
遍历器Iterator就是这样一种机制。它属于一种接口规格任何数据结构只要部署这个接口就可以完成遍历操作即依次处理该结构的所有成员。它的作用有两个一是为各种数据结构提供一个统一的、简便的接口二是使得数据结构的成员能够按某种次序排列。在ES6中遍历操作特指for...of循环Iterator接口主要供for...of消费。
Iterator的作用有三个一是为各种数据结构提供一个统一的、简便的访问接口二是使得数据结构的成员能够按某种次序排列三是ES6创造了一种新的遍历命令for...of循环Iterator接口主要供for...of消费。
遍历器的遍历过程是这样的它提供了一个指针默认指向当前数据结构的起始位置。也就是说遍历器返回一个内部指针。第一次调用遍历器的next方法可以将指针指向到第一个成员第二次调用next方法就指向第二个成员直至指向数据结构的结束位置。每一次调用都会返回当前成员的信息具体来说就是返回一个包含value和done两个属性的对象。其中value属性是当前成员的值done属性是一个布尔值表示遍历是否结束。
Iterator的遍历过程是这样的。
1创建一个指针指向当前数据结构的起始位置。也就是说遍历器的返回值是一个指针对象。
2第一次调用指针对象的next方法可以将指针指向数据结构的第一个成员。
3第二次调用指针对象的next方法指针就指向数据结构的第二个成员。
4调用指针对象的next方法直到它指向数据结构的结束位置。
每一次调用next方法都会返回当前成员的信息具体来说就是返回一个包含value和done两个属性的对象。其中value属性是当前成员的值done属性是一个布尔值表示遍历是否结束。
下面是一个模拟next方法返回值的例子。
```javascript
function makeIterator(array){
var nextIndex = 0;
return {
@ -30,21 +39,19 @@ var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
```
上面代码定义了一个makeIterator函数它的作用就是返回数组的遍历器。对数组`['a', 'b']`执行这个函数,就会返回该数组的遍历器it。
上面代码定义了一个makeIterator函数它的作用就是返回数组的指针对象。对数组`['a', 'b']`执行这个函数,就会返回该数组的指针对象it。
遍历器it的next方法用来移动指针。开始时指针指向数组的开始位置。然后每次调用next方法指针就会指向数组的下一个成员。第一次调用指向a第二次调用指向b。
指针对象的next方法用来移动指针。开始时指针指向数组的开始位置。然后每次调用next方法指针就会指向数组的下一个成员。第一次调用指向a第二次调用指向b。
next方法返回一个对象表示当前位置的信息。这个对象具有value和done两个属性value属性返回当前位置的成员done属性是一个布尔值表示遍历是否结束即是否还有必要再一次调用next方法。
next方法返回一个对象表示当前数据成员的信息。这个对象具有value和done两个属性value属性返回当前位置的成员done属性是一个布尔值表示遍历是否结束即是否还有必要再一次调用next方法。
总之,遍历器是一个对象,具有next方法。调用next方法就可以遍历事先给定的数据结构。
总之,指针对象具有next方法。调用next方法就可以遍历事先给定的数据结构。
由于遍历器只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。
由于Iterator只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。
```javascript
function idMaker(){
var index = 0;
@ -61,17 +68,15 @@ it.next().value // '0'
it.next().value // '1'
it.next().value // '2'
// ...
```
上面的例子中idMaker函数返回的对象就是遍历器但是并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。
上面的例子中,遍历器idMaker函数返回的指针对象,并没有对应的数据结构,或者说遍历器自己描述了一个数据结构出来。
在ES6中有些数据结构原生提供遍历器比如数组即不用任何处理就可以被for...of循环遍历有些就不行比如对象。原因在于这些数据结构部署了System.iterator属性详见下文。凡是部署了System.iterator属性的数据结构就称为部署了遍历器接口。调用这个接口就会返回一个遍历器
在ES6中有些数据结构原生提供遍历器比如数组即不用任何处理就可以被for...of循环遍历有些就不行比如对象。原因在于这些数据结构原生部署了System.iterator属性详见下文,有些没有。凡是部署了System.iterator属性的数据结构就称为部署了遍历器接口。调用这个接口就会返回一个指针对象
如果使用TypeScript的写法遍历器接口、遍历器和next方法返回值的规格可以描述如下。
如果使用TypeScript的写法遍历器接口Iterable、指针对象Iterator和next方法返回值的规格可以描述如下。
```javascript
interface Iterable {
[System.iterator]() : Iterator,
}
@ -84,19 +89,17 @@ interface IterationResult {
value: any,
done: boolean,
}
```
### 默认Iterator接口
## 数据结构的默认Iterator接口
Iterator接口的目的就是为所有数据结构提供了一种统一的访问机制即for...of循环详见下文。当使用for...of循环遍历某种数据结构时该循环会自动去寻找Iterator接口。
ES6规定默认的Iterator接口部署在数据结构的Symbol.iterator属性或者一个数据结构只要具有Symbol.iterator属性就可以认为是“可遍历的”iterable。也就是说调用Symbol.iterator方法就会得到当前数据结构的默认遍历器。Symbol.iterator本身是一个表达式返回Symbol对象的iterator属性这是一个预定义好的、类型为Symbol的特殊值所以要放在方括号内请参考Symbol一节
ES6规定默认的Iterator接口部署在数据结构的`Symbol.iterator`属性,或者一个数据结构只要具有`Symbol.iterator`属性就可以认为是“可遍历的”iterable。也就是说调用`Symbol.iterator`方法,就会得到当前数据结构的默认遍历器。`Symbol.iterator`本身是一个表达式返回Symbol对象的iterator属性这是一个预定义好的、类型为Symbol的特殊值所以要放在方括号内请参考Symbol一节
在ES6中有三类数据结构原生具备Iterator接口数组、某些类似数组的对象、Set和Map结构。
```javascript
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
@ -104,7 +107,6 @@ iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
```
上面代码中变量arr是一个数组原生就具有遍历器接口部署在arr的Symbol.iterator属性上面。所以调用这个属性就得到遍历器。
@ -116,7 +118,6 @@ iter.next() // { value: undefined, done: true }
一个对象如果要有可被for...of循环调用的Iterator接口就必须在Symbol.iterator的属性上部署遍历器方法原型链上的对象具有该方法也可
```javascript
class RangeIterator {
constructor(start, stop) {
this.value = start;
@ -143,7 +144,6 @@ function range(start, stop) {
for (var value of range(0, 3)) {
console.log(value);
}
```
上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数执行后返回当前对象的遍历器。
@ -151,7 +151,6 @@ for (var value of range(0, 3)) {
下面是通过遍历器实现指针结构的例子。
```javascript
function Obj(value){
this.value = value;
this.next = null;
@ -196,7 +195,6 @@ for (var i of one){
// 1
// 2
// 3
```
上面代码首先在构造函数的原型链上部署Symbol.iterator方法调用该方法会返回遍历器对象iterator调用该对象的next方法在返回一个值的同时自动将内部指针移到下一个实例。
@ -204,7 +202,6 @@ for (var i of one){
下面是另一个为对象添加Iterator接口的例子。
```javascript
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
@ -224,27 +221,22 @@ let obj = {
};
}
};
```
对于类似数组的对象存在数值键名和length属性部署Iterator接口有一个简便方法就是`Symbol.iterator`方法直接引用数值的Iterator接口。
```javascript
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
```
如果Symbol.iterator方法返回的不是遍历器解释引擎将会报错。
```javascript
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function
```
上面代码中变量obj的Symbol.iterator方法返回的不是遍历器因此报错。
@ -263,7 +255,7 @@ while (!$result.done) {
上面代码中ITERABLE代表某种可遍历的数据结构$iterator是它的遍历器。遍历器每次移动指针next方法都检查一下返回值的done属性如果遍历还没结束就移动遍历器的指针到下一步next方法不断循环。
### 调用默认iterator接口的场合
## 调用默认Iterator接口的场合
有一些场合会默认调用iterator接口即Symbol.iterator方法除了下文会介绍的for...of循环还有几个别的场合。
@ -288,7 +280,6 @@ let [first, ...rest] = set;
扩展运算符(...也会调用默认的iterator接口。
```javascript
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
@ -297,7 +288,6 @@ var str = 'hello';
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
```
上面代码的扩展运算符内部就调用iterator接口。
@ -305,9 +295,7 @@ let arr = ['b', 'c'];
实际上这提供了一种简便机制可以将任何部署了iterator接口的数据结构转为数组。也就是说只要某个数据结构部署了iterator接口就可以对它使用扩展运算符将其转为数组。
```javascript
let arr = [...iterable];
```
**3其他场合**
@ -319,7 +307,7 @@ let arr = [...iterable];
- Map(), Set(), WeakMap(), WeakSet()
- Promise.all(), Promise.race()
### 原生具备iterator接口的数据结构
## 原生具备Iterator接口的数据结构
《数组的扩展》一章中提到ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。
@ -359,7 +347,6 @@ iterator.next() // { value: undefined, done: true }
可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。
```javascript
var str = new String("hi");
[...str] // ["h", "i"]
@ -380,17 +367,15 @@ str[Symbol.iterator] = function() {
[...str] // ["bye"]
str // "hi"
```
上面代码中字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(...返回的值变成了bye而字符串本身还是hi。
### Iterator接口与Generator函数
## Iterator接口与Generator函数
部署`Symbol.iterator`方法的最简单实现,还是使用下一节要提到的Generator函数。
`Symbol.iterator`方法的最简单实现,还是使用下一章要介绍的Generator函数。
```javascript
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
@ -414,16 +399,17 @@ for (let x of obj) {
}
// hello
// world
```
### return()throw()
上面代码中,`Symbol.iterator`方法几乎不用部署任何代码只要用yield命令给出每一步的返回值即可。
遍历器除了具有next方法必备还可以具有return方法和throw方法可选
## 遍历器的return()throw()
for...of循环如果提前退出通常是因为出错或者有break语句或continue语句就会调用return方法。如果一个对象在完成遍历前需要清理或释放资源就可以部署return方法
遍历器返回的指针对象除了具有next方法还可以具有return方法和throw方法。其中next方法是必须部署的return方法和throw方法是否部署是可选的
throw方法主要是配合Generator函数使用一般的遍历器用不到这个方法。请参阅《Generator函数》的章节。
return方法的使用场合是如果for...of循环提前退出通常是因为出错或者有break语句或continue语句就会调用return方法。如果一个对象在完成遍历前需要清理或释放资源就可以部署return方法。
throw方法主要是配合Generator函数使用一般的遍历器用不到这个方法。请参阅《Generator函数》一章。
## for...of循环

View File

@ -2,14 +2,15 @@
## 基本用法
ES6原生提供了Promise对象。所谓Promise对象就是代表了某个未来才会知道结果的事件通常是一个异步操作并且这个事件提供统一的API可供进一步处理。
Promise在JavaScript语言早有实现ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
有了Promise对象就可以将异步操作以同步操作的流程表达出来避免了层层嵌套的回调函数。此外Promise对象提供的接口使得控制异步操作更加容易。Promise对象的概念的详细解释请参考[《JavaScript标准参考教程》](http://javascript.ruanyifeng.com/)
所谓Promise就是一个对象用来传递异步操作的消息。它代表了某个未来才会知道结果的事件通常是一个异步操作并且这个事件提供统一的API可供进一步处理
ES6的Promise对象是一个构造函数用来生成Promise实例。
有了Promise对象就可以将异步操作以同步操作的流程表达出来避免了层层嵌套的回调函数。此外Promise对象提供的接口使得控制异步操作更加容易。
ES6规定Promise对象是一个构造函数用来生成Promise实例。
```javascript
var promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value);
@ -23,7 +24,6 @@ promise.then(function(value) {
}, function(value) {
// failure
});
```
上面代码中Promise构造函数接受一个函数作为参数该函数的两个参数分别是resolve方法和reject方法。如果异步操作成功则用resolve方法将Promise对象的状态从“未完成”变为“成功”即从pending变为resolved如果异步操作失败则用reject方法将Promise对象的状态从“未完成”变为“失败”即从pending变为rejected