目录
一、定义
ECMA-262(ES6)给出的定义是:
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
翻译成中文就是:Promise对象是用来作为延迟(包含异步)运算的最终结果的占位符。
标准给出的定义是抽象的,我给出的定义是:
Promise对象是一个异步操作的容器,是一个包含对未来事件的结果(可以是异步的),是一个穿越时间循环存在的对象。Promise有三种状态:Pending
(进行中)、Resolved
(已完成,又称Fulfilled)和Rejected
(已失败)。并且Promise是异步编程的解决方案,或者说主要是用同步的方式来书写异步。
基本的概念本篇教程不再复述,本篇不是入门教程,如果你此前不了解Promise,请先自行了解。
二、关于Promise
1.首先Promise是一个构造函数,可以生成Promise实例。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数。resolve
函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
2.Promise.prototype.then()和Promise.prototype.catch()
(1)resolved状态的Promise会回调后面的第一个then;
(2)rejected状态的promise会回调后面的第一个catch。
(3) then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。
/**
* 用setTimeout模拟异步操作
*/
const promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(1);
}, 500)
})
promise
.then(function (result) {
console.log(result)
})
.catch(function (err) {
})
以上代码先返回一个pending状态的Promise,500ms后输出1,这也是一个事件循环,稍后会介绍。
3.任何一个rejected状态且后面没有catch的Promise都会造成浏览器(或者node)环境的全局错误,执行then和catch会返回一个新的Promise,该Promise最终状态根据then和catch回调函数的执行结果决定。
(1)若回调函数的最终结果是throw,则该promise是rejected状态;
(2)若回调函数的最终结果是return,则该Promise是resolved状态;
(3)若回调函数最终return了一个Promise,该Promise会和回调函数return的Promise的状态保持一致。
4.如果是多次异步调用,需要多个then,但是只需要一个catch。
任何一个then中返回的promise对象rejected都会被catch捕获。
/**
* promise的链式调用
*/
function getResult(round) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.2) {
const error = new Error('failed');
error.round = round;
reject(error);
} else {
resolve('success');
}
}, 500)
})
}
getResult(1)
.then(()=> {
return getResult(2);
})
.then(()=> {
return getResult(3);
})
.then(()=> {
if (Math.random() > 0.1) {
const error = new Error('keyboard')
error.round = 'keyboard'
throw error
}
})
.catch((err)=> {
console.log('cry at ' + err.round)
})
5.并发异步Promise.all,第一个catch只能捕获第一个失败的结果。
Promise.all([
getDecide('father').catch(() => { }),
getDecide('mother'),
getDecide('wife'),
]).then(() => {
console.log('family all agree')
}).catch((err) => {
console.log(err.name + ' not agree');
})
/**
*获取是否同意结果
*/
function getDecide(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.2) {
const error = new Error('disagree');
error.name = name;
reject(error);
} else {
resolve('agree');
}
}, Math.random() * 400)
})
}
6.Promise.prototype.finally
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
7.Promise.race
当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolveed),也可以是失败(rejected),这要取决于第一个完成的方式是两个中的哪个。
如果传的参数数组是空,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((data) => {
resolve(data);
return;
}, (err) => {
reject(err);
return;
});
}
}
});
}
8.async/await的执行结果是一个Promise,是Promise的语法糖。async/await是异步编程的终极解决方案,以同步的方式写异步。
(1)使用try/catch可以捕获await的错误
(2)await可以以同步的写法获取Promise的执行结果
(3)await可以“暂停”async function的执行
(4)如果要并行异步,await Promise.all([异步1,异步2,...])
9.Promise.prototype.finally
ES8标准,Promise.prototype.finally方法返回一个Promise
。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise
是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在then()
和catch()
中各写一次的情况。CSDN给出的例子请求数据中,不管请求成功与否都将请求标志设置为false。
let isLoading = true;
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); })
.finally(function() { isLoading = false; });
三、事件循环中的Promise
事件循环指的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。"Event Loop是一个程序结构,用于等待和发送消息和事件。是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。
异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
事件循环中的异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。
宏任务(macrotask):
script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
微任务(microtask):
Promise、 MutaionObserver、process.nextTick(Node.js环境)
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
- 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
- 更新render(每一次事件循环,浏览器都可能会去更新渲染)
- 重复以上步骤
Promise属于微任务,所以会在执行完宏任务后执行。本文提到的一个例子:
const promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(1);
}, 500)
})
promise
.then(function (result) {
console.log(result)
})
.catch(function (err) {
})
上面代码的执行结果是:先返回一个pending状态的Promise,然后输出1。
四、Promise A+规范的实现
根据promise A+规范实现promise,promise A+规范
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function Promise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
_this.onFulfilled = [];
_this.onRejected = [];
function resolve(value) {
if (value instanceof Promise) {
value.then(resolve, reject);
}
setTimeout(() => {
if (_this.currentState === PENDING) {
_this.currentState = FULFILLED;
_this.value = value;
_this.onFulfilled.forEach(cb => cb());
}
});
};
function reject (reason) {
setTimeout(() => {
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.onRejected.forEach(cb => cb());
}
});
};
try {
fn(resolve, reject);
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
let _this = this;
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : value => { throw value };
let promise2 = new Promise((resolve, reject) => {
if (_this.currentState === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(e);
}
});
} else {
if (_this.currentState === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(e);
}
});
} else if (_this.currentState === PENDING) {
_this.onFulfilled.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
_this.onRejected.push(() => {
setTimeout(() => {
try {
let x = onRejected(_this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}
});
return promise2;
};
function resolvePromise(promise2, x, resolve, reject) {
let _this = this;
if (promise2 === x) {
reject(new TypeError("Loop references"));
}
if (x && typeof x === 'object' || typeof x === 'function') {
let flag;
try {
let then = x.then;
if (typeof then === "function") {
then.call( x, (y) => {
if (flag) return;
flag = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (flag) return;
flag = true;
reject(r);
}
);
} else {
if (flag) return;
flag = true;
resolve(x);
}
} catch (e) {
if (flag) return;
flag = true;
reject(e);
}
} else {
resolve(x);
}
}
module.exports = Promise;
如果要对上面的promise实现测试,可以添加如下代码:
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
安装promises-aplus-tests并执行测试
npm install -g promises-aplus-tests
promises-aplus-tests promise.js
结果截图,即通过了872个测试用例:
五、参考资料
1.ECMAScript® 2015 Language Specification
2.Promise 对象,「阮一峰官方博客」
3.Promise,「廖雪峰官方博客」
4.https://juejin.im/user/5c6256596fb9a049bd42c770
6.promise,「MDN」