什么是 promise?html
什么是 async 和 await前端
·同步代码·
,一句话总结,async 函数就是 Generator 函数的语法糖,返回了一个 promise.resolve() 的结果。阮一峰老师的 async 教程上面提到了一个异步的问题,咱们前端er都知道 JavaScript - 是单线程的,若是存在多个任务的时候,就会有任务队列进行排队,而后一一执行任务。git
不着急介绍 promise 的详情,首先咱们从最开始的同步和异步讲起:github
简单的理解ajax
console.log('synchronous'); //咱们能当即获得 synchronous
复制代码
简单的理解chrome
来看一个图数组
一个浏览器一般由如下几个常驻的线程:promise
渲染引擎
和js引擎线程
是不能同时进行的,渲染线程
在执行任务的时候,js引擎线程
会被挂起。由于如果在渲染页面的时候,js处理了DOM,浏览器就不知道该听谁的了渲染引擎
:Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trdent引擎,FireFox用的是Gecko引擎。不一样的引擎对同一个样式的实现不一致,就致使浏览器的兼容性问题。JS引擎:
js引擎能够说是js虚拟机,负责解析js代码的解析和执行。一般有如下步骤:
JS引擎也是不一样的
:Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。总结一点:JavaScript是单线程的,可是浏览器不是单线程的。一些I/O操做,定时器的计时和事件监听是由其余线程完成的。浏览器
开局一张图bash
导图要表达的内容用文字来表述的话: 1.同步和异步任务分别进入不一样的执行"场所" 2.同步的进入主线程,异步的进入Event Table并注册回调函数到 Event Queue 中。 3.当主线程执行完毕之后,而后会去 Event Queue 查询,时候若是存在的函数,放进主线程中继续执行。 4.上述就是event loop的执行
说了这么多文字,不如直接一段代码更直白:
console.log('script start');
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function cb() {
console.log('promise2');
});
console.log('script end');
// script start
// promise1
// script end
// promise2
复制代码
分析这段代码:
首先执行,打印 script start
而后进入 promise 函数打印 promise1,执行 resolve()
在 then 执行的时候咱们把异步回调放进了 event table 中注册相关的回调函数。
new promise 执行完毕,回调函数cb() 进入Event Queue。
执行 打印 script end;
主线程从Event Queue读取回调函数 cb 并执行。
复制代码
微任务
的时候,优先执行 微任务
macro-task(宏任务):包括总体代码script,setTimeout,setInterval micro-task(微任务):Promise,process.nextTick
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/** * script start * async1 start * async2 * promise1 * script end * async1 end * promsise2 * setTimerout */
复制代码
注意几个点
一、js是单线程的。
二、promise被定义后是当即执行的,可是他的resolve是异步的。
三、promise的异步优先级高于setTimeout。
四、async会返回一个promise对象,await关键字会让出线程。
复制代码
- 定义异步函数 async1, 异步函数 async2
1. console.log('script start'); 执行 (1)`script start`
2. setTimeout 执行,异步放入异步队列中,注意这是一个宏任务(咱们标记为 macro1)
3. 执行 async1(), 打印 (2)`async1 start`, 执行 async1() 中的 await async2(): 打印 (3)`async2`;
遇到 await 后面的函数进入任务队列,这里又注册一个微任务(咱们标记为 mico1);到这里 async1() 就执行完了
4. 执行 new Promise:打印 (4)`promise1`,执行 resolve();
而后在 then 中注册回调函数,console.log('promise2') 函数进入任务队列;
注册 event queue(咱们标记为 mico2).这里 new Promise 就执行完了。
5. 执行 console.log('script end');, 打印 (5) `script end`;
6. 上面👆五步把主线程都执行完毕了,而后去event queue 查找有没有注册的函数;
咱们发现了(macro 1, mico1, mico2),按照优先执行微任务的原则,咱们按照这样的顺序执行 mico1 > mico2 > macro1。
打印:(6) `async1 end` (7) `promise2` (8) `setTimeout`
复制代码
[!warning]可能你会在不一样浏览器发现不一样结果,这是由于不一样浏览器和版本的不一样遵循的 promise 规则不一样。这里是按照较新版本的 chrome(68+) 执行的结果,具体参考(www.w3.org/2001/tag/do…
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(() => {
console.log(3);
})
console.log(4);
复制代码
分析
首先Promise新建后当即执行,因此会先输出1,2,而Promise.then()内部的代码在当次事件循环的结尾当即执行,因此会先输出4,最后输出3.
QA:1 2 4 3
const promise = new Promise((resolve, reject) => {
resolve('success1');
reject('error');
resolve('success2');
});
promise.then((res) => {
console.log('then:', res);
}).catch((err) => {
console.log('catch:', err);
})
复制代码
分析
resolve函数Promise对象的状态从“未完成”变为“成功”(即从pending变为resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去; reject函数将Promise对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。 而一旦状态改变,就不会有再变。
因此代码中的reject('error')
;不会有做用。 Promise
只能resolve
一次,剩下的调用都会被忽略。 因此第二次resolve('success')
;也不会有做用。
QA:then:success1
(1) promise 对象初始化状态为 pending
(2) 当调用resolve(成功),会由pending => fulfilled
(3) 当调用reject(失败),会由pending => rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
this.state = PENDING;
this.value = null;
this.reason = null;
const resolve = value => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
}
};
const reject = reason => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
}
};
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
this.state = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.state === PENDING) {
this.state === FULFILLED;
this.value === value;
this.onFulfilledCallbacks.forEach(fuc =>{
fuc();
});
}
};
const reject = reason => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason === reason;
this.onRejectedCallbacks.forEach(fuc =>{
fuc();
})
}
};
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
/* - then方法接受两个参数onFulfilled、onRejected,它们分别在状态由PENDING改变为FULFILLED、REJECTED后调用 - 一个promise可绑定多个then方法 - then方法能够同步调用也能够异步调用 - 同步调用:状态已经改变,直接调用onFulfilled方法 - 异步调用:状态仍是PENDING,将onFulfilled、onRejected分别加入两个函数- - 数组onFulfilledCallbacks、onRejectedCallbacks, - 当异步调用resolve和reject时,将两个数组中绑定的事件循环执行。 */
MyPromise.prototype.then = function(onFulfilled,onRejected){
switch(this.state){
case FULFILLED:
onFulfilled(this.value);
break;
case REJECTED:
onRejected(this.reason);
break;
case PENDING:
this.onFulfilledCallbacks.push(()=>{
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
break;
}
}
// 因为catch方法是then(null, onRejected)的语法糖,因此这里也很好实现
MyPromise.prototype.catch = function(onRejected){
return this.then(null, onRejected);
}
复制代码
Promise.all = function(promises) {
return new Promise(function(resolve, reject) {
var resolvedCounter = 0
var promiseNum = promises.length
var resolvedValues = new Array(promiseNum)
for (var i = 0; i < promiseNum; i++) {
(function(i) {
Promise.resolve(promises[i]).then(function(value) {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues)
}
}, function(reason) {
return reject(reason)
})
})(i)
}
})
复制代码
Promise.all([a,b,c].map(p => p.catch(e => {...})))
.then(res => {...})
.catch(err => {...});
复制代码
function loadImageAsync(url) {
return new Promise(function(resolve,reject) {
var image = new Image();
image.onload = function() {
resolve(image)
};
image.onerror = function() {
reject(new Error('Could not load image at' + url));
};
image.src = url;
});
}
复制代码
衍生:这里须要先并发请求3张图片,当一张图片加载完成后,又会继续发起一张图片的请求,让并发数保持在3个,直到须要加载的图片都所有发起请求。咱们应该怎么作?