前言

Promise 作为一个前端必备技能,不管是从项目应用还是面试,都应该对其有所了解与使用。

常常遇到的面试五连问:

  • 说说你对 Promise 理解?
  • Promise 的出现解决了什么问题?
  • Promise 有哪些状态?
  • 你用过 Promise 哪些方法?
  • 如何实现一个 Promise ?

什么是 Promise?

Promise 是异步编程的一种解决方案:从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。

Promise 有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造 Promise 实例后,它会立即执行。

一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱。

这时候我们的Promise 就应运而生、粉墨登场了。

Promise 的基本使用

Promise 是一个构造函数,自己身上有 allrejectresolve 这几个眼熟的方法,原型上有 thencatch 等同样很眼熟的方法。

首先创建一个 new Promise 实例

let p = new Promise((resolve, reject) => {
// 可以做一些异步操作,例如发送AJAX请求,这里使用 setTimeout 模拟
setTimeout(() => {
let num = Math.ceil(Math.random() * 10); // 生成 1-10 随机数
if (num <= 5) {
resolve('成功');
} else {
reject('失败');
}
}, 2000);
});

Promise 的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:

resolve:异步操作执行成功后的回调函数;

reject:异步操作执行失败后的回调函数;

then 链式操作的用法

p.then((data) => {
console.log('resolve', data); // 'resolve', '成功'
}, (err) => {
console.log('reject', err); // 'reject', '失败'
})

then 中传了两个参数,then 方法可以接受两个参数,第一个对应 resolve 的回调,第二个对应reject 的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到两种结果。

catch 的用法

我们知道Promise 对象除了then 方法,还有一个catch 方法,它是做什么用的呢?其实它和then 的第二个参数一样,用来指定reject 的回调。用法是这样:

p.then((data) => {
console.log('resolve', data); // 'resolve', '成功'
}).catch((err) => {
console.log('reject', err); // 'reject', '失败'
});

效果和写在 then 的第二个参数里面一样。不过它还有另外一个作用:在执行 resolve 的回调(也就是上面then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个 catch 方法中。请看下面的代码:

p.then((data) => {
console.log('resolve', data);
console.log(test);
}).catch((err) => {
console.log('reject', err);
}); // 当成功时先后打印
resolve 成功
reject ReferenceError: test is not defined

resolve 的回调中,我们 console.log(test),而 test 这个变量是没有被定义的。如果我们不用 Promise,代码运行到这里就直接在控制台报错了,不往下运行了,也就是说进到catch方法里面去了,而且把错误原因传到了 reject 参数中。即便是有错误的代码也不会报错了,这与我们的 try/catch 语句有相同的功能。

finally 的用法

finally 方法返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected ,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在 thencatch 中各写一次的情况。

// 加载 loading

let isLoading = true;

// 假装模拟AJAX请求
function myRequest(){
return new Promise((resolve, reject) => {
setTimeout(() => {
let num = Math.ceil(Math.random() * 10); // 生成 1-10 随机数
if (num <= 5) {
resolve('成功');
} else {
reject('失败');
}
}, 1000);
})
} myRequest().then(function(data) { console.log(data); })
.catch(function(error) { console.log(error); })
.finally(function() {
// 关闭loading
isLoading = false;
console.log('finally');
});

resolve 的用法

Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是 thenable(即带有"then" 方法),返回的promise会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

// 示例 1 基本使用

const p = Promise.resolve("Success");

p.then(function(value) {
console.log(value); // "Success"
}, function(value) {
// 不会被调用
}); // 示例 2 resolve 一个数组 var p = Promise.resolve([1,2,3]);
p.then(function(arr) {
console.log(arr[0]); // 1
}); // 示例 3 resolve 另一个Promise let original = Promise.resolve(33);
let cast = Promise.resolve(original);
cast.then(function(value) {
console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast)); /*
* 打印顺序如下,这里有一个同步异步先后执行的区别
* original === cast ? true
* value: 33
*/ // 示例 4 resolve thenable 并抛出错误 // Resolve一个thenable对象 const p1 = Promise.resolve({
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p1 instanceof Promise) // true, 这是一个Promise对象 p1.then(function(v) {
console.log(v); // 输出"fulfilled!"
}, function(e) {
// 不会被调用
}); // Thenable在callback之前抛出异常 // Promise rejects const thenable = { then: function(resolve) {
throw new TypeError("Throwing");
resolve("Resolving");
}}; const p2 = Promise.resolve(thenable); p2.then(function(v) {
// 不会被调用
}, function(e) {
console.log(e); // TypeError: Throwing
}); // Thenable在callback之后抛出异常 // Promise resolves const thenable = { then: function(resolve) {
resolve("Resolving");
throw new TypeError("Throwing");
}}; const p3 = Promise.resolve(thenable); p3.then(function(v) {
console.log(v); // 输出"Resolving"
}, function(e) {
// 不会被调用
});

reject 的用法

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

Promise.reject(new Error('fail')).then(function() {
// not called
}, function(error) {
console.error(error); // Stacktrace
});

all 的用法

谁跑的慢,以谁为准执行回调。all 接收一个数组参数,里面的值最终都算返回 Promise 对象。

Promise.all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。看下面的例子:

let p = new Promise((resolve, reject) => {
setTimeout(() => {
let num = Math.ceil(Math.random() * 10); // 生成 1-10 随机数
if (num <= 5) {
resolve('成功');
} else {
reject('失败');
}
}, 1000);
}); let pAll = Promise.all([p, p, p]); pAll.then((data) => {
console.log(data); // 成功时打印: ['成功', '成功', '成功']
}, (err) => {
console.log(errs); // 只要有一个失败,就会返回当前失败的结果。 '失败'
})

有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。在这里可以解决时间性能的问题,我们不需要在把每个异步过程同步出来。

all 缺点就是只要有一个任务失败就会都失败。

allSettled 的用法

Promise.allSettled ****方法返回一个在所有给定的 promise 都已经fulfilledrejected后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

const p1 = Promise.resolve(3);
const p2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); Promise.allSettled([p1, p2]).
then((results) => results.forEach((result) => console.log(result.status))); // 执行后打印
// "fulfilled"
// "rejected"

any 的用法

Promise.any 接收一个 promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promiseAggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和 Promise.all 是相反的。

const pErr = new Promise((resolve, reject) => {
reject("总是失败");
}); const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
}); const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
}); Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value); // '很快完成'
})

race 的用法

Promise.race ****方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise就会解决或拒绝。也可理解 谁跑的快,以谁为准执行回调。

race 的使用场景:比如我们可以用race 给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

    // 假设请加某个图片资源
function requestImg() {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = function() {
resolve(img);
}
// 尝试输入假的和真的链接
img.src = '**';
})
}
// 延时函数,用于给请求计时
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, 5000)
})
} Promise.race([requestImg(), timeout()]).then((data) => {
console.log(data); // 成功时 img
}).catch((err) => {
console.log(err); // 失败时 "请求超时"
})

如何实现一个Promise

1、创建一个 MyPromise 类,传入 executor(执行器)并添加 resolve 和 reject 方法


class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
} // 更改成功后的状态
resolve = () => {}
// 更改失败后的状态
reject = () => {}
}

2、添加状态和 resolve、reject 事件处理

// 定义三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected'; class MyPromise {
constructor(executor){
executor(this.resolve, this.reject)
} // 储存状态,初始值是 pending
status = PENDING; // 成功之后的值
value = null; // 失败之后的原因
reason = null; // 更改成功后的状态
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
} // 更改失败后的状态
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
}
}

3、.then 的实现

then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value); // 调用成功回调,并且把值返回
} else if (this.status === REJECTED) {
onRejected(this.reason); // 调用失败回调,并且把原因返回
}
}

上面三步已经简单实现了一个 Promise我们先来调用试试:

const promise = new MyPromise((resolve, reject) => {
resolve('success')
reject('err')
}) promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
}) // 打印结果:resolve success

经过测试发现如果使用异步调用就会出现顺序错误那么我们怎么解决呢?

4、实现异步处理

// 定义三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected'; class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
} // 成功存放的数组
onResolvedCallbacks = [];
// 失败存放法数组
onRejectedCallbacks = [];
// 储存状态,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null; // 更改成功后的状态
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onRejectedCallbacks.forEach((fn) => fn()); // 调用成功异步回调事件
}
} // 更改失败后的状态
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach((fn) => fn()); // 调用失败异步回调事件
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value); //调用成功回调,并且把值返回
} else if (this.status === REJECTED) {
onRejected(this.reason); // 调用失败回调,并且把原因返回
} else if (this.status === PENDING) {
// onFulfilled传入到成功数组
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value);
})
// onRejected传入到失败数组
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
}

修改后通过调用异步测试没有

const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
}) promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
}) // 等待 2s 输出 resolve success

但是如果使用链式调用 .then 就会发现有问题,而原生的 Promise 是支持的。 那么我们如何支持呢?

链式调用示例:

const promise = new MyPromise((resolve, reject) => {
resolve('success')
}) function other () {
return new MyPromise((resolve, reject) =>{
resolve('other')
})
}
promise.then(value => {
console.log(1)
console.log('resolve', value)
return other()
}).then(value => {
console.log(2)
console.log('resolve', value)
})
// 第二次 .then 将会失败

5、实现 .then 链式调用

class MyPromise {
... then(onFulfilled, onRejected) {
// 为了链式调用这里直接创建一个 MyPromise,并 return 出去
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
const x = onFulfilled(this.value);
resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
}
} function resolvePromise(x, resolve, reject) {
// 判断x是不是 MyPromise 实例对象
if (x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
x.then(resolve, reject)
} else {
resolve(x)
}
}

6、也可以加入 try/catch 容错

    ...
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch(error) {
this.reject(error)
}
}

最后

本文就先到这里了,后续有时间再补充 .alL.any 等其他方法的实现。

相关推荐

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

作者:雨中愚

链接:https://juejin.cn/post/6995923016643248165

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一文了解Promise使用与实现的更多相关文章

  1. Promise 异步(asynchronous )编程

    概述 Promise.all(iterable) 方法返回一个promise,该promise会等iterable参数内的所有promise都被resolve后被resolve,或以第一个promis ...

  2. Promise杂记

    更好的阅度体验 前言 API Promise特点 状态跟随 V8中的async await和Promise 实现一个Promise 参考 前言 作为一个前端开发,使用了Promise一年多了,一直以来 ...

  3. promise待看文档备份

    http://swift.gg/2017/03/27/promises-in-swift/ http://www.cnblogs.com/feng9exe/p/9043715.html https:/ ...

  4. Javascript - Promise学习笔记

    最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下.   一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...

  5. ABP文档 - Javascript Api - AJAX

    本节内容: AJAX操作相关问题 ABP的方式 AJAX 返回信息 处理错误 HTTP 状态码 WrapResult和DontWrapResult特性 Asp.net Mvc 控制器 Asp.net ...

  6. 深入理解jQuery、Angular、node中的Promise

    最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...

  7. 大白话讲解Promise(二)理解Promise规范

    上一篇我们讲解了ES6中Promise的用法,但是知道了用法还远远不够,作为一名专业的前端工程师,还必须通晓原理.所以,为了补全我们关于Promise的知识树,有必要理解Promise/A+规范,理解 ...

  8. ready是先执行的,load后执行,DOM文档的加载步骤

    在jq中在文档载入完毕后有这几种方式去执行指定函数: $(document).ready(function() { // ...代码... }); //document ready 简写 $(func ...

  9. 【转】Unity中的协同程序-使用Promise进行封装(三)

    原文:http://gad.qq.com/program/translateview/7170967 译者:崔国军(飞扬971)    审校:王磊(未来的未来) 在这个系列的最后一部分文章,我们要通过 ...

  10. 【转】Unity中的协同程序-使用Promise进行封装(二)

    原文:http://gad.qq.com/program/translateview/7170970 译者:王磊(未来的未来)    审校:崔国军(飞扬971)   在上一篇文章中,我们的注意力主要是 ...

随机推荐

  1. WebGL与three.js

    前面学习了一些webgl的基础知识,现在就用一下three.js写一个小例子,记录一下学习的过程. 效果图: 1.去github下载three.js,然后将它加载到网页中 <script src ...

  2. Oozie 快速入门

    设想一下,当你的系统引入了spark或者hadoop以后,基于Spark和Hadoop已经做了一些任务,比如一连串的Map Reduce任务,但是他们之间彼此右前后依赖的顺序,因此你必须要等一个任务执 ...

  3. MVC控制下输出图片、javascript与json格式

    /// <summary> /// 输出图片 /// </summary> /// <returns></returns> public ActionR ...

  4. 从 Java 代码逆向工程生成 UML 类图和序列图

    from:http://blog.itpub.net/14780914/viewspace-588975/ 本文面向于那些软件架构师,设计师和开发人员,他们想使用 IBM® Rational® Sof ...

  5. php魔术方法——属性重载方法

    php有一类很神奇的方法,这些方法是保留方法,通常不会在外部被显式调用,他们使用双下划线(__)开头,他们被称为魔术方法(Magic Methods).php官方也不建议定义其他双下划线开头的方法. ...

  6. JAVA混型和潜在类型机制

    一.混型 ①.定义 二.利用JAVA如何实现混型 ①.代理   ②.装饰器模式  ③.动态代理模式   ④.装饰器模式与代理模式的区别 三.潜在类型机制 ①.定义 四.JAVA的潜在类型机制的补偿 ① ...

  7. nhibernate+autofac+mvc的demo

    想自己做一个小的demo.目的是能够提供一个系统架构,在这个基础上,可以快速开发一些小型的系统.

  8. JS一周游~(基础、运算符、条件语句)

    一.基础篇 JavaScript 基于浏览器(客户端).基于(面向)对象{没有继承}.事件驱动(要有对象).脚本语言(灵活多变) 1.作用 表单的验证,减轻服务端的压力 添加页面动画效果 动态更改页面 ...

  9. JXLS 2.4.0系列教程(四)——拾遗 如何做页面小计

    注:阅读本文前,请先阅读第四篇文章. http://www.cnblogs.com/foxlee1024/p/7619845.html 前面写了第四篇教程,发现有些东西忘了讲了,这里补回来. 忘了讲两 ...

  10. SpringMVC RedirectView的使用以及源码分析

    看一段普通的代码,我们访问controller中的一个方法后,重定向到另外一个controller或者视图. @RequestMapping(params="method=index&quo ...