写作背景
其实我最开始想写的并不是Promise这篇文章,而是想总结一些axios相关的知识,但是写文章必须有所依据,想要去讲好axios,那么对他的前置知识一定要了解,所以我想不妨把这做成一个系列,于是就有了从Promise,Ajax开始,再去讲Axios的打算。其实相信大家做前端开发的一定对Promise不陌生了,所以大家伙也不妨跟着我对Promise这个基础知识进行回顾。 本文将包括:
Promise介绍Promise特点Promise使用- 根据前文的使用方式和特性,手写
Promise
话不多说,我们直接开始!
Promise介绍
Promise起源与用途
Promise最早在社区提出和实现,在ES6写入了语言标准。
tip: ES6即ECMA-262第六版,这一版包含了这个规范有史以来最重要的一批增强特性,Javascript是ECMA-262规范的实现
Promise是异步编程的解决方案,比传统的回调函数解决方式更加合理更加强大更加优雅。- 语法上: 使用Promise构造函数对异步操作进行封装以生成Promise实例
- 功能上: promise对象用来封装一个异步操作并提供统一的API,使得各种异步操作都可以用同样的方式进行处理
常见的异步编程场景
- fs文件操作
require('fs').readFile('./index.html',(err,data)=>{ // 回调函数 })
- Ajax操作
$.get( /api/getUser',(data)=>{ //handleData(); })
- 定时器
setTimeout(() => { console.log('timeout'); },1000);
为什么使用Promise
- 支持链式调用,将异步操作以同步操作的流程表达出来,可以解决回调地狱问题
什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行结果是嵌套的回调的执行条件
// 回调地狱典型场景 asyncFunc1(opt,(...args1) => { asyncFunc2(opt,(...args2) => { asyncFunc3(opt,(...args3) => { asyncFunc4(opt,(...args4) => { //TODO: some opt }) }) }) })
回调地狱的缺点:
不便于阅读,不便于异常处理,不利于身心愉快
- 指定回调函数的方式更加灵活
传统方式: 必须在启动异步任务之前指定
Promise: 可以随时监听异步任务的状态,随时指定回调函数,一个或者多个。
- Promise提供统一的api,使得控制异步操作更加容易。
提供了哪些api在后续的使用中会详细阐述
Promise特点
Promise的特性
- 对象状态不受外界影响
Promise对象代表一个异步操作,有三种状态:pending进行中,fulfilled成功,rejected失败 只有异步操作结束才能改变状态,其他任何操作都不能改变。状态存储在Promise对象的[[PromiseState]]属性中。
// Promise的两个属性: let promiseA = new Promise((resolve,reject)=>{resolve();}) // 状态对应promiseA的[[PromiseState]]字段。 let promiseB = new Promise((resolve,reject)=>{resolve(1111);}) // [[PromiseResult]]的值为 1111,这个字段用于存储 resolve(val)或者reject(val)的参数[val]
tips: Promise本意是承诺,也是因为此原因,想一想这是多么浪漫的名字。
- 一旦状态改变,就不会再发生变化
Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise的缺点
- 一旦新建,立即执行,无法中途取消
Promise内部抛出的错误,无法反映到外部pending状态时无法知道进展到哪一个阶段
Promise使用
Promise实例创建
const promise = new Promise((resolve, reject) => { if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
- resolve函数:将Promise对象的状态从“未完成”变为“成功”(即从
pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; - reject函数:将Promise对象的状态从“未完成”变为“失败”(即从
pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise.prototype.then
Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数
最终会返回一个新的Promise对象
tip: then中的回调函数是可选的,不一定要提供
promise.then(function(value) { // fulfilled状态的处理 }, function(error) { // rejected状态的处理 });
Promise.prototype.catch
catch用于处理状态为rejected的回调函数
最终会返回一个新的Promise对象
promise.catch((err)=>{ handleReject(); // })
Promise.resolve
返回成功或者失败的Promise对象
let promiseA = Promise.resolve(1); // 如果传入的参数为非Promise类型的对象,则返回的结果为成功的Promise对象 let PromiseB = Promise.resolve(new Promise((resolve,reject)=>{ reject('err'); }) // 如果传入的参数为Promise对象,则参数Promise返回的结果就是 Promise.resolve返回的结果 // 比如这时return 一个[[PromiseResult]]的值为err的Promise对象
Promise.reject
返回一个失败的Promise对象
let PromiseA = Promise.reject(new Promise((resolve,reject)=>{ resolve('err'); }) // 无论传入是啥,就返回一个失败的Promise对象,[[PromiseResult]]的值为 Promise.reject的参数
Promise.all
接收的参数是由n个Promise对象的数组。
tips: 返回结果是新的promise,只有所有的Promise对象都成功才成功,只要有一个失败了就直接失败
let promise1 = Promise.resolve(1); let promise2 = Promise.resolve(2); let promise3 = Promise.reject(3); const res = Promise.all([promise1,promise2,promise3]); //此时输出为 [[PromiseState]]是rejected,[[PromiseResult]]是3的Promise const res = Promise.all([promise1,promise2]); //此时输出为 [[PromiseState]]是fulfilled,[[PromiseResult]]是[1,2]的Promise
Promise.race
接收的参数是由n个Promise对象的数组。
tips: 返回结果是新的promise,第一个完成的promise的结果状态就是最终结果的状态。
let promise1 = Promise.resolve(1); let promise2 = Promise.resolve(2); let promise3 = Promise.reject(3); const res = Promise.race([promise1,promise2,promise3]); //此时输出为 [[PromiseState]]是fulfilled,[[PromiseResult]]是1的Promise
常见问题
如何改变Promise对象的状态
- resolve() // pending => fulfilled
- reject() // pending => rejected
- 抛出错误 throw 'err' // pending => rejected
promise.then() 的返回结果
- 如果抛出异常,则返回rejected的Promise对象
- 如果返回的是非promise类型的任意值,则返回状态为resolved的Promise对象
- 如果返回的是一个新的promise,则该promise的结果会成为新的promise结果
promise为什么可以链式调用
因为then,catch,all,race等等所有的promise的api的返回值是新的promise对象。
所以可以继续打点调用promise的方法,以此种方式将任务串联起来
promise的异常穿透
- 当使用promise的then进行链式调用时,可以在最后指定失败的回调
- 前面的任何错误都会在最后传到失败的回调中去处理
let p1 = Promise.resolve(1); p1.then((value)=>{ console.log(11); }).then((value)=>{ throw 'err'; }).then((value)=>{ console.log(22); }).catch(err=>{ console.log(err); }) //输出: 11 err
中断promise链
let p1 = Promise.resolve(1); p1.then((value)=>{ console.log(11); }).then((value)=>{ console.log(22); }).then((value)=>{ console.log(33); }).catch(err=>{ console.log(err); }) //输出:11 22 33
那我们怎么去中断这个回调函数的联调呢
let p1 = Promise.resolve(1); p1.then((value)=>{ console.log(11); }).then((value)=>{ console.log(22); return new Promise(()=>{}); }).then((value)=>{ console.log(33); }).catch(err=>{ console.log(err); }) //输出:11 22
答案就是返回一个状态为pending的promise对象