前言

刚开始的时候自己对 Promise 的了解不是太深,看到 Promise 就头疼,然后看阮一峰老师的 ES6 标准入门的时候根本看不下去,应该是自己之前的水平太差了,不过最近在看的时候发现基本都能理解了,所以尽可能以一个小白的观点整理一下,希望能帮助更多人理解,这篇文章适合对 Promise 了解的不多,但是想了解的人。如果能耐心读下去的话肯定会对你了解 Promise 有所帮助。

1.1 什么是 Promise

首先我们要知道 Promise 是什么,它可以解决什么问题。

假设有这么一个场景,你需要向后台请求三个字符串,然后需要等到这三个请求的结果都返回然后拼接字符串在进行相关的操作,你该怎么做?

可能你会在 ajax 请求里面的回调里面发起第二个请求,然后第二个请求的回调发起第三个请求,最后在第三个请求的回调里面进行相关操作。是不是想一想就觉得写起来很别扭,而用 Promise 的话就很好解决了


var result = Promise.all([ajax1,ajax2,ajax3]).then(()=>{// Todo
})

那么,Promise 到底是什么呢,来看一下定义

Promise 是异步编程的一种解决方案,比起传统的解决方案——回调函数和事件,它更合理且更强大,这一点从上面的例子也可以看出来。

Promise 简单来说就是一个容器,里面放着某个未来才会结束的事件的结果(通常是一个异步操作的结果)。比如说你做数学题的时候遇到了一个难题,然后你把这个难题交给一个大佬,然后你继续做你的事情,然后大佬做出来(或者做不出来)以后将结果告诉你,你再进行相应操作。这个你交给别人做等别人做完返回给你结果的难题就相当于一个 Promise 对象。写成代码如下

let promise = new Promise(function(resolve, reject){let problem = new Problem(); // 你不会做的题目if( solve(problem) )  return resolve('solve'); // 大佬尝试解决这个问题,如果解出来传一个 solve 给你else return reject(new Error('not solve')) // 大佬做不出来,传一个 not resolve 给你
});promise.then(function(value){// value 就是大佬传过来的值。然后用大佬解出来的结果进行相关操作
}).catch(function(err){// err 就是大佬解不出来传回的值。假设大佬解不出来进行相关操作
})

它有两个特点,一是它代表的是一个异步的操作,有三种状态:Pending(进行中),Fulfilled(已成功)和 Reject(已失败)。只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这种状态,这也是 Promise 的由来,代表无法改变。

二是状态一旦改变就不会再次改变,并且再次调用的时候回立即获得结果。有两种状态改变的可能:从 Pending 变为 Fuilfilled 或者从 Pending 变为 Rejected。状态变化以后就处于 Resolved(已定型)的阶段。这就相当于你让大佬做题,大佬要么不会,要么会,会的话你什么时候问他他都会直接告诉你结果,而不是再去算一遍,不会的话他也是直接告诉你结果,也不会去重新算一遍。

但是 Promise 有两个缺点,一个是一旦新建 Promise 就会立即执行,无法中途取消。
再者,当处于 Pending 状态的时候,无法得知当前进展到哪一个阶段(刚刚开始还是即将完成)。对应到前面的例子的话就是,你把这道题交给大佬了,大佬一定会做,大佬就是喜欢钻研,有题目了一定要得到结果,所以才能称得上是大佬。

还有一个就是如果不设置回调函数, Promise 内部抛出的错误不会反应到外部。

接下来介绍一些 Promise 的基本用法。

1.2 基本用法

直接上代码

let promise = new Promise(function(resolve, reject){// todoif(/*异步操作成功*/){resolve(value);} else {reject(error);}
})
// 等同于下面这种方式,因为既然想了解 Promise 的话应该对箭头函数有所了解,所以后面会采用下面这种方式。
let promise = new Promise((resolve, reject)=>{// todoif(/*异步操作成功*/){resolve(value);} else {reject(error);}
})

Promise 构造函数接收一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们也是两个函数,有 javascript 引擎提供,不用自己部署

resolve 的作用将 Promise 对象的状态从 “未完成” 变成 “成功”,并将成功时的结果作为参数传出去。

reject 的作用是将 Promise 对象的状态从 “未完成” 变成 “失败”。并将失败的结果作为参数传出去。

Promise 实例生成以后,可以用 then 方法分别指定 Resolved 状态和 Rejected 状态的回调函数。

promise.then(val => {// success
},err =>{// fail
})

then 方法接收两个回调函数作为参数,第一个回调函数是 Promise 对象状态变为 Fulifilled 时调用,第二个回调函数是 Promise 对象状态变为 Rejected 时调用。第二个参数可选,一般采用 catch 捕捉错误,尽量不使用第二个参数。这两个 函数都接受 Promise 对象传出的值作为参数。

下面是一个简单的例子

var drink = true;
const promise = new Promise((resolve,reject)=>{if(drink) return resolve(drink);else return reject(new Error('err'));
})promise.then(val => console.log(val))
.catch(err => console.log(err));

上面的例子创建了一个 Promise 对象,如果 drink 是 true 的话就将 drink 的值传出去,如果是 false 的话,传出 err。

还有一点要注意的是 Promise 对象中的代码会立即执行,看下面的例子

let promise = new Promise((resolve,reject)=>{console.log('Promise');resolve();
})promise.then(() => console.log('Resolved'));console.log('Hi');// Promise
// Hi
// Resolved

上述代码中,Promise 新建后会立即执行,所以先输出 Promise,然后 then 方法指定的回调函数将会在当前脚本所有同步任务执行完成后才会执行,所以 Resolved 最后输出。这部分的话可以看看宏任务微任务相关内容。

下面来看一个异步加载图片的例子

function loadImgAsync(url){let promise = new Promise((resolve,reject) =>{var img = new Image();img.src = url;img.onload = resolve(img);img.onerror = reject(new Error('Could not load image at ' + url));});return promise;
}

在上述代码中,封装了一个图片加载的异步操作,如果加载成功就调用 resolve 方法,如果出错了,就调用 reject 方法。下面来试一下

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Promise</title>
</head>
<body><div class=main ></div><script>let url = 'https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg';let loadImg = loadImgAsync(url)loadImg.then((img)=>{let div = document.querySelector('.main');div.appendChild(img);console.log('success');});function loadImgAsync(url){let promise = new Promise((resolve,reject) =>{var img = new Image();img.onload = resolve(img);img.onerror = reject(new Error('Could not load image at ' + url));img.src = url;});return promise;}</script>
</body>
</html>

效果如下

假设 url 有问题呢
我们修改 url 为一个不存在的地址

例如 let url = 'https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.png';

由这个例子可以看出,resolve 和 reject 调用时如果带有参数,那么这些参数会被传递给回调函数。一般情况下,reject 函数的参数通常是 Error 对象的实例,表示抛出错误;resolve 函数的参数除了正常的值外,还可能是另外一个 Promise 对象,如下所示

var p1 = new Promise((resolve,reject) =>{// todo
});var p2 = new Promise((resolve,reject) =>{// todoresolve(p1);
});

上述代码中,p2 的 resolve 方法将 p1 作为参数,即一个异步操作的结果是返回另一个异步操作

此时 p1 的状态就会传给 p2。也就是说,p1 的状态决定了 p2 的状态。如果 p1 的状态是 Pending,那么 p2 就会等待 p1 的状态改变,如果 p1 已经是 Resolved 或者 Rejected,那么 p2 的回调函数将会立即执行。

var p1 = new Promise((resolve,reject) => {reject(new Error('fail'));
});var p2 = new Promise((resolve,reject) => {resolve(p1);
});p2.then(result => console.log(result))
.catch(error => console.log(error))
// Error:fail

上面的代码中,由于 p2 返回的是 p1,所以 p2 的状态无效,由 p1 的状态决定 p2 的状态,所以后面的 then 语句都是针对后者(p1)的。p1 变为 rejected,触发 catch 方法指定的回调函数。

还有一点要注意就是调用 resolve 和 rejecte 并不会终止 Promise 函数执行

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

所以一般在 resolve 和 rejecte 前面加上 return

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

这样后面的语句就不会执行了。

1.3 Promise.prototype.then()

下面来看一下 promise 对象的方法,首先是 then 方法,它有两个参数,第一个参数是成功时候调用的函数,第二个是失败时候调用的函数,它的返回是一个新的 Promise 实例,因此可以采用链式写法,即 then 方法后面在调用另一个 then 方法

new Promise((resolve,reject)=>{return resolve({a:1});
}).then(val => console.log(val.a))
.then(val => console.log(val));// 1

1.4 Promise.prototype.catch()

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

 p.then(val => console.log('fulfilled:',val)).catch(err => console.log('rejected:',err));// 等同于p.then(val => console.log('fulfilled:',val)).then(null,err => console.log('rejected:',err));

下面是一个例子

var promise = new Promise((resolve,reject)=>{throw new Error('test');
});promise.catch(err => console.log(err));// Error:test
// 等同于下面两种写法
var promise = new Promise((resolve,reject)=>{try {throw new Error('test');} catch(e) {reject(e);}
});promise.catch(err => console.log(err));// 或var promise = new Promise((resolve,reject) =>{reject(new Error('test'));
});
promise.catch(err => console.log(err));

比较以上两种写法,可以发现 rejecte 方法的作用等同于抛出错误。如果 Promise 的状态已经变成 Resolved,再抛出错误时无效的。

var promise = new Promise((resolve,reject) =>{resolve('ok');throw new Error('test');
});
promise.then( val => console.log(val))
.catch( err => console.log(err));
// ok

上面代码中,Promise 在 resolve 语句后面在抛出错误,并不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就会保持下去。
Promise 对象的错误具有 “冒泡” 的性质,会一直向后传递,直到被捕获为止,也就是说,错误总是会被下一个 catch 语句捕获。

需要注意的是,catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法。

var someAsyncThing = function(){return new Promise((resolve,reject)=>{// 下一行会报错,因为 x 没有声明resolve( x + 2 );});
};someAsyncThing().catch(err => console.log('oh no',err))
.then(()=>console.log('carry on'));
// oh no ReferenceError: x is not defined
// carry on

1.5 Promise.resolve()

有时候需要将现有对象转换为 Promise 对象,这个时候就需要用到 Promise.resolve 方法。

Promise.resolve('foo')

等价于

new Promise(resolve => resolve('foo'))

Promise.resolve 方法的参数分为以下四种情况

  • 参数是一个 Promise 实例

如果参数是 Promsie 实例,则不做任何修改,直接返回

  • 参数是一个 thenable 对象

thenable 对象指的是具有 then 方法的对象,如下面这个对象

let thenable = {then:function(resolve,reject){resolve(42);}
}

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

let thenable = {then:function(resolve,reject){resolve(42);}
}let p1 = Promise.resolve(thenable);p1.then(val => console.log(val)); // 42

上述代码中,thenable 对象的 then 方法执行后,对象 p1 的状态就变成 resolved,从而立即执行最后的 then 方法指定的回调函数,输出 42;

  • 参数不是具有 then 方法的对象或者不是对象

如果对象是一个基本类型的值,或者是一个不具有 then 方法的对象,那么 Promise.resolve 方法返回一个新的 Promise 对象,状态为 Resolved。

var promise = Promise.resolve('Hello');promise.then(val => console.log(val));
// Hello

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

  • 不带任何参数

Promise.resolve 方法允许在调用时不带任何参数,直接返回一个 Resolved 状态的 Promise 对象。

所以如果想获得一个 Promise 对象,比较方便的方法就是直接调用 Promsie.resolve 方法

var p = Promise.resolve();p.then(()=>{// todo
})

上面代码中的变量 p 就是一个 Promise 对象。要注意的是,立即 resolve 的 Promise 对象是在本轮 “事件循环” 结束时,而不是在下一轮事件循环开始时,这里可以参考事件循环的相关知识。

1.6 Promise.reject()

Promise.reject 方法也会返回一个新的 Promise 实例,状态为 Rejected。

var promise = Promise.reject('出错了');// 等同于var promise = new Promise((resolve,reject) => reject('出错了'));p.then((null,err) => console.log(err))
// 出错了

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

注意:Promise.reject 方法的参数会原封不动地作为 reject 的理由变为后续方法的参数,这一点与 Promise.resolve 方法不一致。

const thenable = {then(resolve,reject){reject('出错了');}
};Promise.reject(thenable)
.catch(err => {console.log( e === thenable )
})
// true

1.7 Promsie.all()

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

Promise.all 接收一个数组作为参数,如果该参数不是数组,就会调用 Promise.resolve 方法将参数转为 Promise 实例,再进一步处理(Promise.all 方法的参数不一定是数组,但是必须有 Iterator 接口,且返回的每个成员都是 Promise 实例)

var promise = new Promise([p1,p2,p3]);

promise 的状态有 p1,p2,p3 共同决定,分为两种情况

  1. 只有三个状态都变成 Fulfilled,promise 的状态才会变成 Fulfilled,此时,p1,p2,p3 的返回值组成一个数组,传递给 promise 的回调函数。
  2. 只要其中一个被 Rejected,promise 的状态就会变成 Rejected,此时第一个被 Rejected 的实例的返回值会传递给 promise 的回调函数

我们来用之前的异步加载图片做一个具体的例子

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Promise</title>
</head>
<body><div class=main ></div><script>let img1 = loadImgAsync('http://img.zcool.cn/community/01b34f58eee017a8012049efcfaf50.jpg@1280w_1l_2o_100sh.jpg');let img2 = loadImgAsync('http://img.zcool.cn/community/0117e2571b8b246ac72538120dd8a4.jpg@1280w_1l_2o_100sh.jpg');let img3 = loadImgAsync('http://img07.tooopen.com/images/20170316/tooopen_sy_201956178977.jpg');let imgPromise = Promise.all([img1,img2,img3]);imgPromise.then((imgArr) => {let main = document.querySelector('.main');console.log(imgArr);let fragment = document.createDocumentFragment();imgArr.forEach(ele => {fragment.append(ele);});main.append(fragment);}).catch( err => console.log(err) );function loadImgAsync(url){let promise = new Promise((resolve,reject) =>{var img = new Image();img.src = url;img.onload = resolve(img);img.onerror = reject(new Error('Could not load image at ' + url));});return promise;}</script>
</body>
</html>

随便找了三张图,结果如下

上述代码中,我们创建了 3 个 Promise 对象,当这三个 Promise 对象都加载成功的时候才会调用 imgPromise 的 then 方法,然后将三张图片添加到文档碎片中,最后将文档碎片添加到页面。

还有一点要注意的是如果作为参数的 Promise 实例自身定义了 catch 方法,那么它被 rejected 时不会触发 Promise.all 的 catch 方法。

举个栗子

 const p1 = new Promise((resolve,reject) => {resolve('hello');}).then( result => result).catch( err => err);const p2 = new Promise((resolve,reject) => {throw new Error('报错了');}).then( result => result).catch( err => err);Promise.all([p1,p2]).then(result => console.log(result)).catch(err => console.log(err));// ['Hello',Error:报错了]

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

1.8 Promise.race()

Promise.race() 方法同样是将多个 Promise 对象包装成一个新的 Promise 实例。但是只要其中一个实例的状态率先改变,p 的状态就跟着改变,那个率先改变状态的 Promise 实例的返回值就传给 Promise.race() 包装的新对象。

Promise.race 的参数和 Promise.all 一样,如果不是 Promise 实例,就会先调用 Promise.resolve 方法,将参数转为 Promise 对象再进一步处理。

还是以异步加载图片为例,只需要将 Promise.all 改成 Promise.race

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Promise</title>
</head>
<body><div class=main ></div><script>let img1 = loadImgAsync('http://img.zcool.cn/community/01b34f58eee017a8012049efcfaf50.jpg@1280w_1l_2o_100sh.jpg');let img2 = loadImgAsync('http://img.zcool.cn/community/0117e2571b8b246ac72538120dd8a4.jpg@1280w_1l_2o_100sh.jpg');let img3 = loadImgAsync('http://img07.tooopen.com/images/20170316/tooopen_sy_201956178977.jpg');let imgPromise = Promise.race([img1,img2,img3]);imgPromise.then((img) => {let main = document.querySelector('.main');console.log(img);main.append(img);}).catch( err => console.log(err));function loadImgAsync(url){let promise = new Promise((resolve,reject) =>{var img = new Image();img.src = url;img.onload = resolve(img);img.onerror = reject(new Error('Could not load image at ' + url));});return promise;}</script>
</body>
</html>

这里可以看出,当 img1 的状态改变时,imgPromise 就调用了它的回调函数

总结

这篇文章写的有些繁琐了,因为基本把 Promise 的基础知识都整理出来了,其实主要需要掌握 Promise 的基本用法,以及 Promise.all 和 Promise.race 就行,如果看完了想实践一下或者觉得概念太抽象想通过具体的例子来看的话可以参考这篇文章
八段代码彻底掌握 Promise

浅析 Promise相关推荐

  1. 浅析Promise用法

    浅析Promise用法 要理解Promise要知道没有Promise的回调地狱 如何插入一段漂亮的代码片 Promise语法与then的用法 所谓Promise,简单说就是一个容器,里面保存着某个未来 ...

  2. 浅析Promise原理

    Promise原型对象 在浏览器控制台输入如下代码,可以看到Promise原型对象信息. var p = new Promise(()=>{}); console.log(p) Promise原 ...

  3. Promise原理浅析

    Promise介绍 项目相关demo和代码地址 介绍 Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算..一个Promise对象代表着一个还未完成,但预 ...

  4. 浅析JaveScript中的Promise对象 暮雨清秋

    前言 本文旨在简单讲解一下javascript中的Promise对象的概念,特性与简单的使用方法.并在文末会附上一份符合PromiseA+规范的Promise对象的完整实现. 注:本文中的相关概念均基 ...

  5. 定时器和promise_浅析setTimeout与Promise

    关于JavaScript异步编程,前文解析过了JavaScript并发模型,该并发模型基于事件循环.正好在Stackoverflow上回答了一个关于setTimeout与Promise执行顺序相关的问 ...

  6. 50行代码串行Promise,koa洋葱模型原来这么有趣?

    1. 前言 大家好,我是若川,最近组织了源码共读活动<1个月,200+人,一起读了4周源码>,感兴趣的可以加我微信 ruochuan12 参与,长期交流学习. 之前写的<学习源码整体 ...

  7. C++多线程std::async、std::future、std::packaged_task、std::promise

    std::async std::async用于创建异步任务,实际上就是创建一个线程执行相应任务,默认立即开始执行. std::async就是异步编程的高级封装,std::async的操作,其实相当于封 ...

  8. JavaScript中deferred对象浅析

    JavaScript中deferred对象浅析 一.deferred对象 1.1deferred对象 1.1.1基本概念 deferred对象是JQuery的回调函数解决方案,解决了如何处理耗时操作的 ...

  9. 浅析微信小程序生命周期之应用生命周期

    浅谈微信小程序生命周期之应用生命周期 参考:微信小程序官方文档 微信小程序生命周期可分为应用生命周期.页面生命周期和组件生命周期,本文结合微信官方文档浅析微信小程序的应用生命周期.从注册一个小程序生命 ...

最新文章

  1. 何时使用margin和padding?
  2. c语言答案填空选择,C语言试题配答案
  3. Geometers Anonymous Club CodeForces - 1195F (闵可夫斯基和)
  4. CLR_via_C#.3rd 翻译[1.5 本地代码生成工具NGen.exe]
  5. 杭电1856More is better
  6. django view
  7. (整理)ubuntu10.10安装低版本的编译器(低版本)(gcc)(ubuntu)
  8. sed系列:行或者模式匹配删除特定行
  9. iOS 深拷贝、浅拷贝、自定义对象拷贝简介
  10. php if多条件_通过PHP与Python代码对比浅析语法差异
  11. NodeJS运行时抛出: Error: listen EADDRINUSE :::3000
  12. zuc算法代码详解_ZUC算法原理及实现过程[共3页]
  13. 计算机cpu天体图,认识一下电脑的CPU,附2019最新CPU天梯图
  14. c语言编程身高体重测量,身高体重测量系统设计.doc
  15. T9智能输入法实现原理和步骤
  16. 保护眼睛颜色的RGB
  17. 2021最新 阿里云邮箱域名解析设置要求
  18. canvas小虫子(利用canvas形成多个形状类似虫子的线条)
  19. Prometheus Operator 配置PrometheusRule告警规则
  20. IT应届实习能不加班?如何逃离996?

热门文章

  1. 全球智慧城市IOT市场规模报告
  2. 此更新不适用您的计算机 win10,高手亲自讲解Win10系统提示此更新不适用于您的详尽处理办法...
  3. sqoop export hive数据同步到oracle的用法
  4. 容器学习Day04-Ubuntu常用命令(二)
  5. easyexcel 简介、中文文档、中英对照文档 下载
  6. 让TXT文本等其他任意格式的文件,不在IE中打开,而直接下载的方法
  7. oracle查看表空间的属性 ,修改表空间的状态
  8. Win10锁屏之后设置自动关闭屏幕的时间
  9. 新MAC苹果M1芯片简要分析(是不是地表最强呢??)
  10. cad应用技巧:图层特性管理器