Promise基础用法

> 简述:

## 什么是Promise?

- Promise是用来处理异步的;

- Promise就是承诺,对未来的承诺;

- 所谓的Promise(承诺),里面保存着未来才会结束的事件的结果;

- Promise是异步编程的一种解决方案; 比传统的解决方案(回调函数和事件)更合理更强大;

- Promise一个对象,从它可以获取异步操作的消息;

- Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了

### 两个特点

- 对象的状态不受外界影响, Promise对象代表一个异步操作;

#### 3种状态

    1 pending     等待态

    2 fulfilled 已成功

    3 rejected  已失败

    只有异步操作的结果,可以决定当前是哪一个状态,任何外部的操作都无法改变这个状态

- 状态一旦改变,就不会也不能再变,任何时候都可以得到这个结果;

Promise的状态,只能从pending变为fulfilled或rejected, fulfilled和rejected不能相互转换;

## 诞生的时代背景 ##

> 在JavaScript的世界中,所有代码都是单线程执行的; 由于这个"缺陷",以至于JavaScript的所有网络操作、浏览器事件等都必须异步执行; 异步意味着在未来的某个时刻得到结果; 在promise之前,我们写的ajax应用(前一个函数的执行结果作为下一个函数的参数),就面临着大量的回调函数的窘境;

## 为解决回调而生 ##

- Promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数;

- Promise对象提供统一的接口,使得控制异步更加容易;

## 不是最perfect, 因为它还是基于回调 ##

- 无法取消Promise,一旦新建就会立即执行,无法中途取消;

- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;

- 当处于pending时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

- 从本质上来说,Promise还是基于回调的

## 基础用法 ##

### 一、构造函数与then ###

- ES6规定,Promise对象是一个构造函数,用来生成Promise实例

- Promise接收一个函数(executor:执行者)作为参数,该函数有两个参数:

    1 resolve(Function)      -> 将Promise对象的状态从"未完成pending"变为"成功resolved",在异步操作成时调用,并将异步操作的结果,作为参数传递出去(供then方法的成功回调接收)

    2 reject(Function)        -> 将Promise对象的状态从"未完成pending"变为"rejected",在异步失败时调用,并将异步操作的错误或失败的原因,作为参数传递出去(供then方法的失败回调接收)

```

const promise = new Promise(function(resolve, reject){

    if(/*异步操作成*/){

        resolve(value); // 成功时调用它会将Promise的状态从"pending"变为"resolved"

    }else{

        reject(err);    // 失败时调用它会将Promise的状态从"pending"变为"rejected"

    }

});

```

- Promise实例生成后,可以用then方法分别指定resolved(参数是成功的结果)状态和rejected(参数是失败的原因)状态的回调函数; 即then方法接收两个回调函数作为参数:

    1 第一个函数,是Promise对象的状态变为resolved时调用(接收成功的结果作为参数)

    2 第二个函数,是Promise对象的状态变为rejected时调用[可选](接收失败的原因或错误作为参数)

```

promise.then((value)=>{

    // value是成功的结果

}, (err)=>{

    // 失败的原因

});

// 示例: demo/1.创建promise实例.js

```

- Promise的状态发生改变时就会触发then方法对应的不同状态的函数参数

### 二、Promise新建后会立即执行 ###

- Promise是同步的,promise.then方法是异步的

```

const promise = new Promise((resolve, reject)=>{

    console.log('Promise');

    resolve();

});

promise.then((value)=>{

    console.log('Success')

});

console.log('Hi!');

// => Promie  Hi! Success

// 上面代码,Promise新建后会立即执行,所以会先输出'Promise', 然后我们为then方法指定了回调函数,因为then方法是异步的,所以它会等待同步代码都执行完之后才执行,所以接下来输出的是'Hi!',当主栈中的同步代码执行完毕之后, 开始执行异步任务(then是个微任务), 因此promise状态改变后成功的回调输出'Success'

// 示例: demo/2.promise创建会立即执行

```

```

// 异步加载图片示例

function loadImageAsync(url){

    return new Promise((resolve, reject) => {

        let image = new Image();

        image.onload = function(){

            resolve(image);  // 如果图片加载成则调用该方法, 将promise的状态改为resolved

        };

        image.onerror = function(){

            reject('创建失败'); // 如果图片加载失败, 则调用该方法, 将promise的状态改为rejected

        };

        image.src = url;

    });

}

loadImageAsync('./timg2.jpg').then((value)=>{

    // 如果状态变为成功态,则将图片添加到页面

    console.log(value);

    document.querySelector('body').appendChild(value);

}, (err)=>{

    // 状态变为失败态,则将提示错误

    console.log('Error:', err);

});

// 示例: demo/3.异步加载图片

```

```

// Promise对象实现Ajax示例

let getJSON = function(){

    return new Promise((resolve, reject)=>{

        $.getJSON('./test.json').then((value)=>{

            console.log(value);

            resolve(value)

        }, (err)=>{

            // console.log('失败回调:',err)

            reject(err);

        });  

    });

};

getJSON().then((data)=>{

    console.log(data)

},(err)=>{

    console.log('My-Error:', err);

});

// 如果调用resolve函数和reject函数时带有参数,那么它们的参数被被传递给回调函数(then的成功回调和失败回调)

// 示例: demo/4.Promise对象实现Ajax

```

- Promise的回调函数中抛出错误或异常,则直接调用then的失败态(reject)回调函数

```

new Promise((resolve,reject)=>{

    throw new Error('错误');

}).then((data)=>{

    console.log(data)

},(err) => {

    console.log('Err ', err);  //Err  Error: 错误....

    return err;

})

```

### 三、resolve函数返回Promise实例 ###

> resolve函数的参数除了正常的值以外,还能是另一个Promise实例

```

const p1 = new Promise(function (resolve, reject) {

  // ...

});

const p2 = new Promise(function (resolve, reject) {

  // ...

  resolve(p1);

})

上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行

```

```

const p1 = new Promise(function (resolve, reject) {

  setTimeout(() => reject(new Error('fail')), 3000)

})

const p2 = new Promise(function (resolve, reject) {

  setTimeout(() => resolve(p1), 1000)

})

p2

  .then(result => console.log(result))

  .catch(error => console.log(error))

// Error: fail

//上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数

// 一言以蔽之: "传递的promise都要执行,直到执行后的结果是普通值(非promise)或抛出异常, 再传递到下一个then中, 执行对应的回调"

```

### 四、调用resolve与reject并不会终结Promise的参数函数(executor)执行 ###

```

new Promise((resolve,reject)=>{

    resolve('success');

    console.log('Hello')

}).then(data=>{

    console.log(data)

});

// => 'Hello' 'success'

// 一般来说, 调用resolve和reject以后,Promise的使命就完成了,后续的操作应该放到then方法里面,而不应该直接写在resolve和reject的后面; 所以,最好在它们前面加上return语句;

```

```

new Promise((resolve,reject)=>{

    return resolve('success');

    console.log('Hello')

}).then(data=>{

    console.log(data)

});

// 此时只输出success

```

### 五、Promise.prototype.then() ###

- Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的;

- then方法的作用是为Promise实例添加状态改变时的回调函数,它的第一个参数是resolve状态的回调函数,第二个参数是rejected状态的回调函数;

- then方法返回的是一个新的Promise实例(注意不是原来那个Promise实例);

- 链式写法,即then方法后面再调用另一个then方法

1、 then链式调用, 第一个then回调的返回结果会作为第二个then回调的参数

```

new Promise((resolve,reject)=>{

    return resolve('success');       // 将状态改变为成功态

}).then((data)=>{

    console.log(data)                // success

    return '成功' + data;

},(err) => {

    console.log('Err', err);

    return err;

}).then((data) => {

    console.log(data)                // 成功success

}, (err) =>{

    console.log("Fail", err)

});

// 上面代码使用then方法, 依次执行两个then的回调函数. 第一个回调完成后,会将返回结果作为参数,传入到第二个回调函数;

```

2、 then的reject回调函数中返回错误,会继续传递到下一个then的成功(resolve)回调**`切记`**

```

new Promise((resolve,reject)=>{

    return reject('fail');  // 执行失败回调

}).then((data)=>{

    console.log(data);         

    return '成功';

},(err) => {

    console.log('Err', err);  // Err fail

    return err;

}).then((data) => {

    console.log('hello',data) // hello fail

}, (err) =>{

    console.log("Fail", err)

});

```

3、then()会多层传递

```

new Promise((resolve,reject)=>{

    resolve('ABC');

}).then().then().then((data)=>{

    console.log(data)

},(err)=>{

    console.log(err)

});

// 输出 "ABC"

```

### 六、Promise.prototype.catch() ###

- Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数

```

new Promise((resolve,reject)=>{

    throw new Error('错误');

}).catch((err)=>{

    console.log('Error:', err); // Error: Error: 错误

});

等同于

new Promise((resolve,reject)=>{

    throw new Error('错误');

}).then(null, (err)=>{

    console.log('Error:', err); // Error: Error: 错误

});

```

- 在Promise的回调函数中直接抛出错误,此时状态变为rejected, 错误会被catch捕获

```

new Promise((resolve,reject)=>{

    throw new Error('错误');

}).catch((err)=>{

    console.log('Error:', err); // Error: Error: 错误

});

```

- then方法指定回调函数, 在运行中抛出错误, 也会被catch捕获

```

new Promise((resolve,reject)=>{

    resolve('ABC');       // 将状态改变为成功态

}).then((data)=>{

    console.log(test)    // 在then的成功回调中执行,输出一个未定义的变量,此时会抛出错误

},(err) => {

    console.log('Err ', err);

}).catch((err)=>{

    console.log('Catch ', err); // 在这里被捕获 => Catch  ReferenceError: test is not defined ....

});

```

- 如果Promise的状态已经变成resolved, 再抛出错误是无效的

```

new Promise((resolve,reject)=>{

    resolve('ABC');

    throw new Error('错误');

}).then((data)=>{

    console.log(data)  // 输出ABC

},(err) => {

    console.log('Err ', err);

}).catch((err)=>{

    console.log('Catch ', err);

});

// 上面代码执行输出了"ABC",说明在状态改变后抛出错误,对结果无影响

// 上面代码中,Promise在resolve语句后面抛出错误,没有被捕获,相当于没有抛出错误;因为Promise的状态一旦改变,就永远保持该状态,不会再变了;

```

- reject的作用等同于抛出错误

```

new Promise((resolve,reject)=>{

    reject('ABC');

}).catch((err)=>{

    console.log('Catch ', err);

});

// 输出: "Catch  ABC"

等同于

new Promise((resolve,reject)=>{

    try{

        console.log('a')

        throw new Error('test');

    }catch(e){

        console.log('b')

        reject(e)

    }

}).catch((err)=>{

    console.log('Catch ', err);

});

// => a b Catch  Error: test

```

### Promise.all() ###

- Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例

- Promise.all方法接受一个数组作为参数(数组中的每一项都是一个promise实例),只有所有的实例状态都变为fulfilled, promise实例的状态才会变成fulfilled, 此时数组中promise实例的返回值会组成一个数组,传递给该promise实例的回调函数, 返回值在数组中的顺序与promise数组中promise实例的顺序一致

```

let p1 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(100);

    }, 2000);

});

let p2 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve('Hello');

    }, 3000);

});

let p3 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(true);

    }, 2000);

});

Promise.all([p1, p2, p3]).then((data)=>{

    console.log(data);  // 等数组中的promise实例都执行完成后,才将状态改变为fulfilled => [ 100, 'Hello', true ]

}, (err)=>{

    console.log(err)

});

// => [ 100, 'Hello', true ]

```

- 如果其中有一个promise实例抛出错误,就会走到Promise.all().catch()方法

```

let p1 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(100);

    }, 2000);

});

let p2 = new Promise((resolve,reject)=>{

    throw Error('错误啦');

});

let p3 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(true);

    }, 2000);

});

Promise.all([p1, p2, p3]).then((data)=>{

    console.log(data)

}).catch(err=>{

    console.log('Promise.all Error:', err)

});

// => Promise.all Error: Error: 错误啦....

// 在p2执行时抛出了异常,那么整个Promise.all()执行状态被改变为rejected, 错误被catch捕获

```

- 如果作为参数的Promise实例,自己定义了catch方法, 那么一旦被rejected,并不会触发Promise.all()的catch方法

```

let p1 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(100);

    }, 2000);

});

let p2 = new Promise((resolve,reject)=>{

    throw Error('错误啦');

}).catch(err=>{

    console.log('p2.Error', err);        // p2.Error Error: 错误啦

});

Promise.all([p1, p2]).then((data)=>{

    console.log(data);                    // [ 100, undefined ]

}).catch(err=>{

    console.log('Promise.all Error:', err)

});

// 上例中,p1在2秒后状态改变为resolve; p2执行时抛出错误,此时p2的状态改变为rejected, 因为它有自己的catch,在执行完catch方法后,状态也变成了resolve, 因此会调用then的指定的回调, 而不会调用catch方法指定的回调函数; 所以,最终打印出: [ 100, undefined ]

```

### Promise.race() ###

- Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例

- 同上面all()方法相比,只要有一个实例率先改变状态,整个promise实例的状态就跟随改变; 与此同时,第一个改变的promise实例(数组中的)的值,被传递给Promise的回调函数

```

let p1 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(100);

    }, 2000);

});

let p3 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(true);

    }, 2500);

});

Promise.race([p1,  p3]).then((data)=>{

    console.log(data)        // 100

}).catch(err=>{

    console.log('Promise.all Error:', err)

});

```

- 如果promise数组中有一个普通值,会立马返回

```

let p1 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(100);

    }, 2000);

});

let p3 = new Promise((resolve,reject)=>{

    setTimeout(() => {

        resolve(true);

    }, 2500);

});

Promise.race([p1,  p3, 10086]).then((data)=>{

    console.log(data)    // 10086

}).catch(err=>{

    console.log('Promise.all Error:', err)

});

// => 10086; 因为我们最终要使用的是普通值或普通对象,而非promsie,所有的实例最终的目的都是要返回普通值, 因此遇到普通值会立马返回

```

### Promise.resolve() ###

- Promise.resolve()可用于将现有对象转换为Promise对象

- `Promise.resolve('foo')`等价于`new Promise(resolve=>resolve('foo'))`

- Promise.resolve方法的参数分成四种情况:

    1、参数是一个 Promise 实例

     - 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例

    2、参数是一个thenable对象(thenable对象指的是具有then方法的对象)

     - Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法

     ```

      let thenable = {

        then: function(resolve, reject) {

          resolve(42);

        }

      };

      let p1 = Promise.resolve(thenable);

      p1.then(function(value) {

        console.log(value);  // 42

      });

      // thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42    

     ```

3、参数不是具有then方法的对象,或根本就不是对象

- 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise对象,状态为resolved

    ```

    const p = Promise.resolve('Hello');

    p.then(function (s){

      console.log(s)

    });

    // Hello

    // 上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数

    ```

    4、不带有任何参数

- Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的Promise对象,所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法

### Promise.reject() ###

- Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

```

const p = Promise.reject('出错了');

// 等同于

const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {

  console.log(s)

});

// 出错了

//上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行

```

- Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数.这一点与Promise.resolve方法不一致

```

const thenable = {

  then(resolve, reject) {

    reject('出错了');

  }

};

Promise.reject(thenable)

.catch(e => {

  console.log(e === thenable)

})

// true

// 上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的"出错了"这个字符串,而是thenable对象

```

### 参考文档 ###

[ECMAScript 6 入门 - Promise对象](http://es6.ruanyifeng.com/#docs/promise)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容