硬核解析Promise对象(这七个必会的常用API和七个关键问题你都了解吗?)

举报
脖子右拧 发表于 2022/04/23 10:29:39 2022/04/23
【摘要】 ​ 目录一、Promise的理解与使用Promise的概念🐾Promise的使用🐧回调地狱问题🐢解决方案🐛Promise的基本用法🐝Promise实践初体验🐜fs模块读取文件🐞使用 promise 封装 ajax 异步请求🐌promise对象状态属性🐙promise对象结果值属性🐠小案例🐟二、Promise APIPromise 构造函数: Promise (excut...

 目录

一、Promise的理解与使用

Promise的概念🐾

Promise的使用🐧

回调地狱问题🐢

解决方案🐛

Promise的基本用法🐝

Promise实践初体验🐜

fs模块读取文件🐞

使用 promise 封装 ajax 异步请求🐌

promise对象状态属性🐙

promise对象结果值属性🐠

小案例🐟

二、Promise API

Promise 构造函数: Promise (excutor) {}🐄

Promise.prototype.then()🐏

Promise.prototype.catch()🐀

Promise.resolve() 🐃

Promise.reject()  🐅

Promise.all() 🐇   

Promise.race() 🐉

三、Promise关键问题 

1.如何修改对象的状态🐐

2.能否执行多个回调🐓

3.改变状态与执行回调的顺序问题🐕

4.then方法返回结果由什么决定 🐖

5.串联多个任务🐁

6.异常穿透🐂

7.如何终断Promise链 🐲



一、Promise的理解与使用🏀

Promise的概念

Promise 是异步编程的一种解决方案,而我们之前的解决方案是啥呢?没错,就是回调函数。Promise要比它更合理和更强大。

这样解释还不够清楚明白,那Promise到底是个啥呢 ❓

简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。


通俗讲,Promise翻译过来就是承诺,它是对未来事情的承诺,承诺不一定能完成(就像你答应你女朋友未来会给她买包,只是想先应付一下,以后买不买不一定呢),但是无论是否能完成都会有一个结果。

Promise的使用

Promise 是异步编程的一种解决方案,主要用来解决回调地狱的问题,可以有效减少回调嵌套。

从语法上说:Promise就是一个构造函数

从功能上说:Promise对象用来封装一个异步操作并可以获取其成功/失败的返回值

首先我们先想一下都有哪些是异步操作 ❓

fs文件操作    数据库操作   ajax请求操作   定时器

上面都是异步操作

fs文件操作 require('fs').readFile('./xiaojie.txt',(err,data)=>{})
数据库操作   $.get('/server',(data)=>{})
定时器   setTimeout(() => {}, 2000);

在promise前,我们进行异步操作时,用的都是纯回调函数的方式来进行的,那我们为什么使用promise呢,使用回调函数不行吗 ❓

注意,这是最重要的,面试必问

  因为Promise支持链式调用,可以解决回调地狱问题  

回调地狱问题

那么什么是回调地狱呢 ❓为啥要解决他呢 ❓Promise又是怎么解决的呢 ❓

下面我们一个一个来解决:

首先我们看下面这段代码,我们想分三次输出数字,每次间隔一秒钟,那我们肯定得这么做:

setTimeout(function () {
    console.log(111);
    setTimeout(function () {
        console.log(222);
        setTimeout(function () {
            console.log(333);
        }, 1000);
    }, 1000);
}, 1000);

代码嵌套,在一个复杂的程序当中,是非常不便于阅读的并且不便于异常处理,因为在每一层我们都需要对错误进行异常处理,可能会写很多重复的代码而且会让缩进格式变得非常冗赘,当嵌套层数过多,会出现“回调地狱”。在这里就体现出用一种新的方式书写代码有多重要。用promise方法去处理这类问题,无疑会更合适。

解决方案

  promise链式调用  

new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(111);
        resolve();
    }, 1000);
}).then(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(222);
            resolve();
        }, 1000);
    });
}).then(() => {
    setTimeout(() =>{
        console.log(333);
    }, 1000);
});

这样最后的输出效果是相同的,而且会把嵌套的代码语句书写改变为按序书写,但不改变其执行的异步过程,这正是promise方法的作用及优点。 


Promise的基本用法

ES6 规定,Promise 是一个构造函数,即然是构造函数那他就可以进行对象实例化。

promise在对象实例化的时候需要接受参数,这个参数是一个函数类型的值

该函数的两个参数分别是 resolve 和 reject 。

resolve是解决的意思,reject是拒绝的意思。

这两个都是函数类型的数据

当异步任务成功时调用resolve,当异步任务失败时调用reject

下面代码创造了一个Promise实例。

const p = new Promise((resolve, reject) => {
  // ... some code
    if (/* 异步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
});

  resolve() 在调用完后,可以将promise对象(p)的状态设置为成功  

  reject() 相反,调用完后,可以将promise对象(p)的状态设置为失败  

我们创建完promise实例后需要调用then方法,他在执行时需要接收两个参数,而且两个参数都是函数类型的值

p.then((value) => {},(error) => {});

第一个参数是对象成功时的回调,第二个参数是对象失败时的回调。


这两个参数都是可选的,不一定要提供。它们都接受 Promise 对象传出的值作为参数。
通俗的说,如果promise对象状态成功就执行第一个回调函数,如果状态失败就执行第二个回调函数


Promise实践初体验

我们先不展开讲解Promise的各种API和原理,先做两个实践应用,简单体验一下Promise。

fs模块读取文件

这个需要我们安装node.js环境,fs模块的作用就是可以对计算机的硬盘进行读写操作

我们的案例要求是这样的:

读取当前目录下的nba.txt文件里的内容:

  传统回调函数形式: 

//引入fs模块
const fs = require('fs');
fs.readFile('./nba.txt', (err, data) => {
    // 如果出错 则抛出错误
    if(err)  throw err;
    //没有出错就输出文件内容
    console.log(data.toString());
});
PS D:\用户目录\Desktop\home\html (2)\4.20> node zyj.js
勇士总冠军

这样的话我们的nba.txt里的内容就输出出来了

  Promise封装形式: 

//封装一个函数 mineReadFile 读取文件内容
//参数:  path  文件路径
//返回:  promise 对象

function mineReadFile(path){
   return new Promise((resolve, reject) => {
       //读取文件
       require('fs').readFile(path, (err, data) =>{
           //判断
           if(err) reject(err);
           //成功
           resolve(data);
       });
   });
}

mineReadFile('./nba.txt')
.then(value=>{
   //输出文件内容
   console.log(value.toString());
}, error=>{
   console.log(error);
});

如果成功的话data就是一个buffer需要tostring方法把它转化为字符串,如果出错就把错误的对象打印出来

当我们输出正常时:

PS D:\用户目录\Desktop\home\html (2)\4.20> node zyj.js
勇士总冠军

我们故意把路径打错,看看错误会输出什么:

PS D:\用户目录\Desktop\home\html (2)\4.20> node zyj.js
[Error: ENOENT: no such file or directory, open 'D:\用户目录\Desktop\home\html (2)\4.20\na.txt'] {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'D:\\用户目录\\Desktop\\home\\html (2)\\4.20\\na.txt'
}

输出的就是error这个错误对象里的内容


使用 promise 封装 ajax 异步请求

<script >
/*可复用的发 ajax 请求的函数: xhr + promise*/
function promiseAjax(url) {
     return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.send();
            xhr.onreadystatechange = () => {
              if (xhr.readyState !== 4) return;
              // 请求成功, 调用 resolve(value)
              if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response);
              } else { // 请求失败, 调用 reject(reason)
                reject(xhr.status);
              }
            }
          })
        }
promiseAjax('http://www.liulongbin.top:3006/api/getbooks').then(data => {
           console.log('显示成功数据', data)
        },
        error => {
           alert(error)
        }) 
</script>

输出结果:

当我们故意把地址打错:


promise对象状态属性

Promise 对象的状态不受外界影响。Promise 对象有三种状态:
  pending (进行中)、fulfilled (已成功) 和 rejected (已失败)。

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

Promise对象的状态改变,只有两种可能:从 pending 变为 fulfilled(resolved)和从 pending 变为 rejected。

只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。


如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

promise对象结果值属性

这是 promise 实例对象中的另一个属性 [promiseResult] ,保存着异步任务 [成功/失败] 的结果

resolve和reject两个方法是可以对实例对象的结果属性进行修改,除了他俩谁也改不了


小案例

在对Promise有个大概了解后,我们看一下下面的代码:

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});

分析:

上面代码中,timeout 方法返回一个Promise实例对象,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后, 计时器会调用 resolve(),并且把结果值(done)传进来,在调用完后,可以将promise对象(p)的状态设置为成功 ,他的状态改变了就会触发then方法绑定的回调函数,把传进来的结果值(done)输出出来。



二、Promise API🏀

Promise 构造函数: Promise (excutor) {}

(1) executor 函数: 执行器 (resolve, reject) => {}

(2) resolve 函数: 内部定义成功时我们调用的函数 value => {}

(3) reject 函数: 内部定义失败时我们调用的函数 reason => {}

重点:executor 会在 Promise 内部立即同步调用 

我们来看下面这段代码

let p = new Promise(function(resolve, reject) {
  console.log('jack');
  resolve();
});

p.then(value=> {
  console.log('andy');
});

console.log('Hi!');

// jack
// Hi!
// andy

上面代码中,Promise 新建后立即执行,所以首先输出的是 'jack'。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以再输出 'Hi','andy' 最后输出。

实际上,这个运行结果相关知识点是 [ 宏任务与微任务 ]  ,单独梳理在下方:

  • 宏任务: setTimeout , setInterval , Ajax , DOM事件
  • 微任务: Promise async/await
  • 微任务执行时机比宏任务要早(先记住)

下面我们看一道面试题:

console.log(100);

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

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

Promise.resolve().then(()=>{
    console.log(300);
})

console.log(400);

// 100 400 300 200 201

大家都做对了吗,如果没有做对的话,再结合一下JS的运行机制就可以轻松理解了:

  • 所有的同步任务都在主线程上执行,行成一个执行栈。
  • 除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。
  • 主线程完成所有任务(执行栈清空),就会读取任务列队,先执行微任务队列在执行宏任务队列。
  • 重复上面三步。


Promise.prototype.then()

then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数

then方法方法返回的是一个新的Promise实例 

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法

let p = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, 'jack'); 
}); 
p.then(value => {
  console.log(value); 
  return 'andy';
}).then(value => console.log(value));
//jack
//andy

then()方法会返回一个新的Promise对象, 所以then后面可以继续跟一个then方法进行链式调用, 如果then方法的回调函数有返回数据,那么这个数据会作为下一个then的回调函数的参数,这块知识点涉及到链式调用,在Promise关键问题第五个串联多个任务里有详细讲解。大家在这里先做一个简单了解。

采用链式的then, 会等待前一个Promise状态发生改变才会被调用

then的回调函数也可能会返回一个Promise对象,这个时候就会覆盖then自己返回的Promise对象,这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

let p = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, 'jack'); 
}); 
p.then(value => {
  console.log(value); 
  return new Promise((resolve,reject) => {
    setTimeout(resolve,1000,'andy');
  });
}).then(value => console.log(value));
//jack
//andy

上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。

因为第二个回调函数有一个定时器,所以一秒钟后状态变为resolved,第二个then方法看到状态改变了才会去执行log输出语句


Promise.prototype.catch()

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

let p = new Promise((resolve, reject) => { 
  reject('what is the matter');
}); 
p.catch(error => {
  console.log(error);
})
//what is the matter

如果Promise 对象状态变为rejected,就会调用 catch() 方法指定的回调函数,处理这个错误。

let p = new Promise((resolve, reject) => { 
  throw new Error('抛出了错误')
}); 
p.catch(error => {
  console.log(error);
})
//Error: 抛出了错误

上面代码中,Promise抛出一个错误,同样会被 catch() 方法指定的回调函数捕获。

  值得注意的是:如果 Promise 状态已经变成 resolved ,再抛出错误是无效的。 

let p = new Promise((resolve, reject) => { 
  resolve('success');
  throw new Error('抛出了错误')
}); 
p.then(value => {
  console.log(value);
}).catch(error => {
  console.log(error);
})
//success

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

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。

我们知道每个then方法都会产生一个新的Promise实例,如果一个Promise对象后面紧跟着多个then方法,它们之中任何一个抛出的错误,都会被最后一个catch()捕获。

  重点:所以,不要在 then() 方法里面定义 Reject 状态的回调函数(即 then 的第二个参数),最   好使用catch方法,这样可以捕获前面then方法执行中的错误。


Promise.resolve()  

有时需要将现有对象转为 Promise 对象,Promise.resolve() 方法就起到这个作用。

let p = Promise.resolve('jack');
console.log(p);

打开控制台,看看上面这段代码输出了什么:

他的结果是一个Promise对象,而且状态为成功,成功的值为我们传入的参数 'jack' 。

Promise.resolve() 等价于下面的写法:

Promise.resolve('jack')
// 等价于
new Promise(resolve => {resolve('jack')})

Promise.resolve() 方法的参数分成两种情况:

参数是一个Promise实例:

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

let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(p1);
console.log(p2);
//Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 'jack'}

参数不是Promise对象或没有任何参数:

Promise.resolve() 方法返回一个新的 Promise 对象,状态为 resolved

const p = Promise.resolve();

p.then(function () {
  // ...
});

上面代码的变量 p 就是一个 Promise 对象。

需要注意的是,立即 resolve() 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。 

setTimeout(function () {
  console.log('jack');
}, 0);

Promise.resolve().then(function () {
  console.log('andy');
});

console.log('david');

// david
// andy
// jack

上面代码中,setTimeout( fn , 0) 下一轮“事件循环”开始时执行,Promise.resolve() 在本轮“事件循环”结束时执行,console.log('david') 是立即执行,因此最先输出。


Promise.reject()   

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

let p1 = Promise.reject('出错啦');
console.log(p1);
//Promise { <rejected> '出错啦' }
const p = Promise.reject('出错啦');
// 等同于
const p = new Promise((resolve, reject) => reject('出错啦'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

即便我们传入的参数是一个成功的Promise对象,他的结果也是失败:

let p1 = Promise.reject(new Promise((resolve, reject) => {
    resolve('ok');
}));
console.log(p1);
//Promise { <rejected> Promise { 'ok' } }

此时我们的Promise对象p1的状态是失败,它失败的结果是我们传入的这个值,也就是这个成功的Promise对象,简单理解就是,不论怎样,Promise.reject()返回的状态一定是rejected 


Promise.all()    

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

const p = Promise.all([p1, p2, p3]);
  • Promise.all() 方法接受一个数组作为参数,
  • p1, p2, p3 都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(111);
const p3 = Promise.all([p1,p2,'andy']);
console.log(p3);

这里我们定义了两个状态为成功的Promise对象 p1和p2,我们想通过Promise.all() 方法将多个 Promise 实例,包装成一个新的 Promise 实例 p3,但是参数数组里的最后一个元素并不是Promise对象而是一个字符串,那么 p3 会输出什么呢?

输出结果如上图所示,就如我们前面提到的,如果数组内有元素不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise 实例,再进行处理。

p 的状态由 p1, p2, p3 决定,分成两种情况:

1. 只有 p1, p2, p3 的状态都变成 fulfilled ,p 的状态才会变成 fulfilled ,此时 p1, p2, p3 的返回值组成一个数组,传递给p的回调函数。

2. 只要 p1, p2, p3 之中有一个为 rejected,p 的状态就变成 rejected ,此时第一个被 reject 的实例的返回值,会传递给p的回调函数。

下面我们来分析一段代码:

const p1 = new Promise((resolve, reject) => {
  resolve('success');
}).then(value => value).catch(reason => reason);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
}).then(value => value).catch(reason => reason);

Promise.all([p1, p2]).then(value => {console.log(value)})
.catch(reason => {console.log(reason)});

上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all() 方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

下面是这段代码的输出结果:

如果p2没有自己的catch方法,就会调用 Promise.all() 的 catch 方法,这个时候输出结果就是这样的:


Promise.race()

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

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1 , p2 , p3 之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 

我们分析一下下面一段代码应该输出什么:

let p1 = new Promise((resolve,reject)=>{
  setTimeout(() => {
    resolve('ok');
  }, 1000);
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('oh year');
const result = Promise.race([p1,p2,p3]);
console.log(result);

我们先看一下p1,p2,p3他们三个谁先改变状态,如果p1先改变状态,那 result 结果就是由p1决定的,如果p1成功那么result就成功,它成功的结果就是result成功的结果。但是因为在我们的例子里p1是一个定时器,是一个异步任务,那肯定不是p1,顺着往下排,p1不行那就肯定p2先改变状态,我们在控制台看看输出的结果:

因为p2的状态是成功,所以result的状态就是fulfilled,因为p2结果是success,所以result结果也是result。



三、Promise关键问题 🏀

1.如何修改对象的状态

  1. resolve(value): 如果当前是 pending 就会变为 resolved
  2. reject(reason): 如果当前是 pending 就会变为 rejected
  3. 抛出异常: 如果当前是 pending 就会变为 rejected(可以抛一个Error实例对象)


2.能否执行多个回调

当 promise 改变为对应状态时都会调用

我们看下面这段代码:

let p = new Promise((resolve, reject) => {  resolve('OK');});

p.then(value => { console.log(value); });
p.then(value => { console.log(value+' again')});

我们给Promise对象p制定了两个回调,那它都会执行么?我们看下控制台:

控制台顺序打印了两个回调里的输出,当且仅当Promise对象改变为对应状态时才会调用。

如果在Promise内部不调用resolve,那他的状态就是pending,这样我们没有改变状态所以一个then回调也不会执行

如果把这段代码改成这样,他还会是一样的结果吗:

let p = new Promise((resolve, reject) => {  resolve('OK');});

p.then(value => {  console.log(value);}).then(value => { console.log(value+' again');});

先看一下输出结果:

为什么第二个回调里的value是undefined呢? 

因为在上面then方法的详解中提到过then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)

如果then方法的回调函数有返回数据,那么这个数据会作为下一个then的回调函数的参数,但是我们上面例子里的第一个then里没有return数据,所以第二个then里就不知道value是谁,如果我们在第一个then里加上return value,这样输出的结果就和原来一样啦。


3.改变状态与执行回调的顺序问题

都有可能, 正常情况下是先指定回调再改变状态, 但也可能先改状态再指定回调

当Promise对象的执行器当中的任务是同步任务,直接调用resolve,这种情况就是先改变Promise对象的状态,然后再去指定回调

如下例代码:

let p = new Promise((resolve,reject) => {
     resolve('success');
});
p.then(value => {console.log(value);});

那什么时候then方法先执行,然后resolve改变状态后执行呢?

就是当执行器函数中是异步任务的时候, 也就是说,我改变状态需要等待一段时间,需要进入对应的异步任务队列去执行的时候,在这种情况下就是then先执行,resolve改变状态后执行

如下例代码:

let p = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('success');
    }, 1000);
});
p.then(value => {console.log(value);});

我们总结一下:

​先指定回调再改变状态(异步):

  1. 先指定回调
  2. 再改变状态
  3. 改变状态后才进入异步队列执行回调函数

先改状态再指定回调(同步):

  1. 改变状态
  2. 指定回调 并马上执行回调

那么我们如何先改状态再指定回调呢?

注意:指定并不是执行

  • 在执行器中直接调用 resolve()/reject() ,不使用定时器等方法,执行器内直接同步操作
  • 延迟更长时间才调用 then() ,在 .then() 这个方法外再包一层例如延时器这种方法

那我们什么时候才能得到数据? 

  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

简单地说,如果Promise对象的执行器里是异步任务,比如说输出语句在定时器里的话,就先指定回调,但是指定不是执行。也得等Promise对象的状态发生改变时,才回去调用回调函数。


4.then方法返回结果由什么决定 

前面我们说了,then方法方法返回的是一个新的Promise实例 ,那新的Promise对象的状态由什么来决定呢,它的返回结果的特点是什么呢?

简单表达:新Promise对象的状态由then()指定的回调函数执行的结果决定

详细表达:

1. 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常

代码示例:

let p = new Promise((resolve,reject) => {
    resolve('success');
});
let result = p.then(value => {
     throw '出错啦';
})
console.log(result);

输出结果:

2.如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值

代码示例:

let p = new Promise((resolve,reject) => {
    resolve('success');
});
let result = p.then(value => {
    return 'jack';
})
console.log(result);

输出结果:

 

3.如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果

代码示例:

let p = new Promise((resolve,reject) => {
    resolve('success');
});
let result = p.then(value => {
    return new Promise((resolve,reject) => {
        reject('no');
    })
})
console.log(result);

输出结果:


5.串联多个任务

1. promise 的 then()返回一个新的 promise, 可以看成 then()的链式调用


2. 通过 then 的链式调用串连多个同步/异步任务,这样就能用then()将多个同步或异步操作串联成一个同步队列

 我们先看下面这段代码:

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          resolve('ok');
     },1000)
});
p.then(value => {
     return new Promise((resolve,reject) => {
          resolve('success');
     })
}).then(value => {
     console.log(value);
})

输出结果:

因为第一个then返回的是一个新的Promise对象, 那么这个 promise 的结果就会成为新 promise 的结果,所以最后会输出success。

接下来我们对上面的代码再加一个then回调:

let p = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('ok');
    },1000)
});
p.then(value => {
    return new Promise((resolve,reject) => {
        resolve('success');
    })
}).then(value => {
    console.log(value);
}).then(value => {
    console.log(value);
})

最后这个value的输出会是什么呢? 答案是 undefined

我们知道then的返回结果是一个Promise对象,它这个Promise对象的状态是由它指定的回调函数的返回值来决定的,第二个then的回调函数的返回值是空,就是undefined,undefined显然不是一个Promise类型的对象,我们之前说过,如果返回的是非 promise 的任意值, 新 promise 变为 resolved,也就是状态是成功的,那他的成功的结果就是返回的结果undefined。因为第二个then返回的Promise对象成功了,就会执行最后一个then的第一个回调,并且输出他成功的结果,也就是undefined。这就是then的链式调用


6.异常穿透

  • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调
  • 前面任何操作出了异常, 都会传到最后失败的回调中处理

示例代码:

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          reject('出错啦');
     },1000)
});
p.then(value => {
     console.log(000);
}).then(value => {
     console.log(111);
}).then(value => {
     console.log(222);
}).catch(error => {
     console.warn(error);
})

我们先看一下输出结果:

他输出的结果刚好是第一个Promise当中他失败的结果的值,中间的环节我们不需要管,不用指定失败的回调,只需要在最后指定一个失败的回调就可以处理失败的结果。这种现象就属于异常穿透。

比如我们在中间环节抛出一个错误,看看最后的catch能不能捕获到 :

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          resolve('ok');
     },1000)
});
p.then(value => {
     throw '出错啦';
}).then(value => {
     console.log(111);
}).then(value => {
     console.log(222);
}).catch(error => {
     console.warn(error);
})

输出结果:

第一个then回调里抛出的错误是由最后的catch方法的回调来处理的,这就是异常穿透。


7.如何终断Promise链 

当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有4个then(),但其中有条件判断,如当我符合或者不符合第2个then条件时,要直接中断链式调用,不再走下面的then,该如何?

 方法: 在该回调函数中返回一个 pending 状态的 Promise 对象 

示例代码:

let p = new Promise((resolve,reject) => {
     setTimeout(() => {
          resolve('ok');
     },1000)
});
p.then(value => {
     console.log(111);
}).then(value => {
     console.log(222);
     return new Promise(()=>{})
}).then(value => {
     console.log(333);
}).then(value => {
     console.log(444);
})

输出结果:

示例分析:

中断Promise链的方法有且只有一种方式,那就是必须返回一个pending状态的Promise对象。因为如果返回的是一个成功的Promise对象,就算返回的结果都是undefined但是then里的回调还是会执行,唯独只有pending状态的Promise对象不一样,因为如果回调里返回一个pending,那么这个then返回的也是状态为pending的Promise对象,如果第一个(本题中)then返回的是pending状态的Promise对象,那后续的then方法的回调就都不能执行,因为状态没有改变,状态不改变就都不能执行。

  简单地说:把当前状态变成pending,那么后面的then和catch就不执行  

  到这里我们Promise的第一部分就完成了,我们学习了Promise相关的API和一些关键问题,但是这些对于我们去大厂面试来说还远远不够。Promise的第二部分我们将试着去自定义封装Promise。我们先学会如何怎么去使用它,再从原理入手学着怎么去实现它。

希望看到这里的同学能够来个一键三连支持一下小杰同学!😁😁😁

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。