一、先看一个应用场景
发送一个请求获得用户id, 然后根据所获得的用户id去执行另外处理。当然这里我们完全可以使用回调,即在请求成功之后执行callback;
但是如果又添加需求呢?比如获得用户id之后,再发送请求去获取用户名,之后再获取用户其他信息。。。。这就陷入了callback-hell,而且代码很难维护。promise可以解决这样的问题。
function getUserId() { return new Promise(function(resolve) { //异步请求 http.get(url, function(res) { resolve(res.id) }) }) } getUserId().then(function(id) { //其他处理 })
上面的回调是不是看起来不叫顺手了。
二、Promise是什么?
它简单说就是一个容器,就是一个构造函数。里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
也可以说Promise 是异步编程的一种解决方案,其实我们经常使用$.ajax()就是一种promise的解决方案
更加详细的内容可以看阮一峰的《Promise对象》。
三、can i use
现在更多的浏览器对Promise提供了原生的支持。axios基于 Promise 的 HTTP 请求就是一个常用的应用栗子。
三、写点demo
当对概念有点燃的时候,回头看看自己写过的demo,肯定有不一样的理解,这即是博客的一个好处之一
function getNumber(){ return new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ var num = Math.ceil(Math.random()*10); if(num<=5){ resolve(num); } else{ reject('num的值大了'); } }, 2000); }); } getNumber() .then(function(data){ console.log('resolved'); console.dir(data); }) .catch(function(data){ console.log('rejected'); console.dir(data); });
封装一个函数,参数是定时器的时间参数
function sleep (time) { return new Promise((resolve) => setTimeout(resolve, time)); } sleep(5000).then(function(val){ console.log(`${val}秒之后起床`); });
四、怎么实现一个promise?
1、最简单的promise雏形
function Promise(fn){
if(fn && typeof fn !== 'function'){ throw new Error('Promise resolver is not a function') }; //回调函数数组,肯能同时有多个回调 this.callBacks = []; //执行函数 const resolve = val =>{ //执行全部回调 setTimeout(()=>{ this.callBacks.forEach(cb=>{ cb(val) }); },0) }; //将resolve作为实参传入fn中,fn的异步什么时候有结果再去执行resolve fn(resolve); } //注册回调函数,successCallback将会被压进Promise容器中的callBacks队列 Promise.prototype.then = function(successCallback){ this.callBacks.push(successCallback); }; //测试用例 const promise = new Promise(function(resolve){ //模拟一个异步 setTimeout(()=>{ resolve('我是传给Promise内执行函数的参数') },2000) }) promise.then(function(val){ console.log(val); });
说明:
(1)、创建promise实例时候传进的函数函数被赋予一个函数类型的参数resolve,resolve接收一个参数,代表异步但返回的结果;当异步操作成功之后,就会执行resolve方法;
(2)、调用then方法,将想要在Promise异步操作成功时执行的回调放入callBacks队列,其实也就是注册回调函数,
(3)、第一步中执行resolve方法就就是执行callbacks数组中的回调函数,依次执行;
(4)、加入延迟机制,通过setTimeout(),保证在resolve
执行之前,then
方法已经注册完所有的回调。
2、加入状态
pending :即将发生的状态
fulfilled : 成功状态
rejected : 失败状态
function Promise(fn){
if(fn && typeof fn !== 'function'){ throw new Error('Promise resolver is not a function') };
//回调函数数组 this.callBacks = []; //一开始的状态是发生的状态 this.status = 'pending'; //立即执行的参数,初始为null this._val = Object.create(null); //执行函数 const resolve = val =>{ //改变状态 this.status = 'fulfill'; //立即执行的参数 this._val = val; //执行全部回调 setTimeout(()=>{ this.callBacks.forEach(cb => { cb(val) }); }) }; //将resolve作为实参传入fn中并执行,fn的异步什么时候有结果再去执行resolve函数 fn(resolve); } Promise.prototype.then = function(successCallback){ if (this.status === 'pending') { this.callBacks.push(successCallback); } successCallback(this._val); }; const promise = new Promise(function(resolve){ //模拟一个异步 setTimeout(()=>{ resolve('我是传给Promise内执行函数的参数') },2000) }) promise.then(function(val){ console.log(val); }); //此时promise实例执行的时候,status已经变为了‘fulfill’,在此之后调用then添加的新回调,都会立即执行。 setTimeout(() => { promise.then(function(val) { console.log(val); }); }, 3000)
3、链式
如果在then函数里面再注入一个promise呢?
主要是对then函数和resolve啊含糊的额改造,看这里~
4、es6实现写法
class CutePromise { constructor(executor) { if (typeof executor !== 'function') { throw new Error('Executor must be a function'); } this.state = 'PENDING'; this.chained = []; const resolve = res => { if (this.state !== 'PENDING') { return; } this.state = 'FULFILLED'; this.internalValue = res; for (const { onFulfilled } of this.chained) { onFulfilled(res); } }; const reject = err => { if (this.state !== 'PENDING') { return; } this.state = 'REJECTED'; this.internalValue = err; for (const { onRejected } of this.chained) { onRejected(err); } }; try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { if (this.state === 'FULFILLED') { onFulfilled(this.internalValue); } else if (this.$state === 'REJECTED') { onRejected(this.internalValue); } else { this.chained.push({ onFulfilled, onRejected }); } } } //test let p = new CutePromise(resolve => { setTimeout(() => resolve('Hello'), 100); }); p.then(res => console.log(res)); p = new CutePromise((resolve, reject) => { setTimeout(() => reject(new Error('woops')), 100); }); p.then(() => {}, err => console.log('Async error:', err.stack)); p = new CutePromise(() => { throw new Error('woops'); }); p.then(() => {}, err => console.log('Sync error:', err.stack));
五、总结
1、Promise的构造函数接收一个函数类型的参数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。
2、resolve()执行时传入的参数,传到了之后调用的.then()中。
3、reject()执行时传入的参数,传到之后调用的.catch()中。在里面抛出代码函数的异常,不会卡死js.
4、调用getNumber()时候返回Promise的实例对象,这样就 支持了了链式的调用。