promise and async详解及执行顺序

395 阅读8分钟

Promise

1.简介

所谓的promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,promise是一个对象,从它可以获取异步操作的消息。promise是一个对象,从他可以获取异步操作的消息。promise提供统一的api,各种异步操作都可以用同样的方法进行处理。

2.特点

  1. 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态:pending(进行中)、resolved(已完成,又称fulfilled)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是promise这个名字的由来,他的英语意思就是承诺,表示其他手段无法改变
  2. 一旦状态改变,就不会再变。任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能,从pending变成resolved和从pending变成rejected.只要这两种情况发生,状态就凝固了,不会再改变了,会一直保持这个结果,就算改变已经发生了,你在对promise对象添加回调函数,也会立即得到这个结果。这与事件完全不同,事件的特别是,如果你错过了他,再去监听是得不到结果的。

3.用法

1.简单用法
var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
        console.log('执行完成');
        resolve('随便什么数据');
    }, 2000);
});

promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve、reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。上面代码,2s后输出“执行完成”,并且调用resolve方法。

2.为何不用callback而用promise
function runAsync(callback){
    setTimeout(function(){
        console.log('执行完成');
        callback('随便什么数据');
    }, 2000);
}

runAsync(function(data){
    console.log(data);
});

callback多层嵌套维护性降低。需要在定义一个callback2,然后给callback传进去。pomise的优势在于,可以在then方法中继续写promise对象并返回,然后继续调用then来进行回调操作。

3.链式操作
function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务1执行完成');
            resolve('随便什么数据1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务2执行完成');
            resolve('随便什么数据2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务3执行完成');
            resolve('随便什么数据3');
        }, 2000);
    });
    return p;            
}
runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

输出结果

异步任务1执行完成
随便什么数据1
异步任务2执行完成
随便什么数据2
异步任务3执行完成
随便什么数据3

在promise中,将数据返回,在下一个then中会调用

在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了,比如我们把上面的代码修改成这样:

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return '直接返回数据';  //这里直接返回数据
})
.then(function(data){
    console.log(data);
});

输出结果

异步任务1执行完成
随便什么数据1
异步任务2执行完成
随便什么数据2
直接返回数据
4.reject方法

用来捕捉回调失败的情况

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的随机数
            if(num<=5){
                resolve(num);
            }
            else{
                reject('数字太大了');
            }
        }, 2000);
    });
    return p;            
}

getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);
5.catch用法

promise对象除了then方法外,还有一个catch方法。它和then的第二个参数一样,用来指定reject回调

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

另一个作用:用来指定reject回调时,如果抛出一场了,并不会报错卡死js,而是会进到这个catch方法中

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

因为somedata没有定义,如果不用promise会报错,不往下运行。但是这里会得到如下结果

resolved
5
rejected
ReferenceError: somedata is not defined
at <anonymous>:5:17
at <anonymous>
6.all方法

具有并行能力,并且在所有异步操作执行后才执行回调

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

异步操作返回的数据会放入到数组results中

输出结果

异步任务1执行完成
异步任务2执行完成
异步任务3执行完成
(3) ["随便什么数据1", "随便什么数据2", "随便什么数据3"]

应用场景:图片flash等静态文件加载后,再进行页面的初始化操作

7.race的用法

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

执行结果

异步任务1执行完成
随便什么数据1
异步任务2执行完成
异步任务3执行完成

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

//请求某个图片资源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延时函数,用于给请求计时
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:

8.Promise.resolve

Promise.resolve(value)方法返回一个以定值解析后的Promise对象。如果返回的是一个promise,那么将返回一个promise;如果这个值是一个thenable,返回的promise会”跟随“这个thenable的对象,采用它的最终状态。否则返回的promise将以此值完成

const p1 = Promise.resolve(123)
p1.then((value) =>{
	console.log(value)//123
})
//equle 
const p1 = new Promise((resolve,reject)=>{
	resolve(123)
})
p1.then((value)=>{
	console.log(value)//123
})

Promise.resolve()和resolve的区别

  • Promise.resolve(thenable)和resolve(thenable)处理结果不同
new Promise((resolve)=>{
	resolve(thenable)
    //eq
    Promise.resolve().then(()=>{
      thenable.then(()=>{
      	
      })
    })
}).then(()=>{
	thenable
})
  • Promise.resolve(non-thenable)和resolve(non-thenable)处理结果相同
//let resolvePromise = new Promise(resolve => {
  //resolve('str')
//})
//works like
let resolvePromise = Promise.resolve('str')

async and await

简介

async函数是使用async关键字声明的函数。async函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意的链式调用promise

特性

  • await关键字值在async函数内有效,如果在async函数体外使用它,就会报错
  • async/await的目的是为了简化promise的使用
  • await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当做该await表达式的返回值。
  • async函数一定会返回一个promise对象,如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中
async function foo(){
	return 1;
}
//equal
function foo () {
	return Promise.resolve(1)
}
async function foo() {
   await 1
}
//equal
function foo() {
   return Promise.resolve(1).then(() => undefined)
}
  • 在await表达式之后的代码可以被认为是存在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值

promise 和 async的经典面试题

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
  // 版本2 bchrome 73 解析成Promise.resolve
  // new Promise((resolve)=>{
  //     console.log('2')
  //     resolve();
  // }).then(()=>{
  //     console.log('1 end')
  // })
  // or 
  // Promise.resolve(console.log(2)).then(() => {
  //     console.log('1 end')
  // })
  
  
  // 版本3 safari chrome 70  解析成resolve
  //  resolve(resolvedPromise) works like 
  //  Promise.resolve().then(() => resolvedPromise.then(resolve, reject));
  // Promise.resolve(console.log(2)).then(() => {
  //     Promise.resolve().then(() => {
  //     })
  // }).then(()=>{
  //     console.log('1 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')

你不知道的javascript 补充

  • 一个任务有时同时完成,有时候异步完成, 这可能导致竞态条件。根据promise的定义,就不必担心这种问题,因为即使是立即完成的promise也无法被同步观察到(then里面的只会是异步调用)
  • 调用过晚
p.then(function () {
  p.then(function() {
      console.log('c');
  })  
  console.log('a')
})
p.then(function () {
    console.log('b')
})
//输出结果:abc
//c无法打断或者抢占b,因为这是promise的运作方式

一个promise决议后,这个promise上所有的通过then()注册的回调都会在下一个异步时机点依次被立即调用。这些回调中的任意一个都无法影响或延误对其他回调的调用

  • promise调度技巧
var p3 = new Promise(function (resolve,reject) {
    resolve('b');
})
var p1 = new Promise(function (resolve,reject) {
    resolve(p3)
})
var p2 = new Promise(function (resolve,reject) {
    resolve('a')
})
p1.then(function (v) {
    console.log(v)
})
p2.then(function (v) {
    console.log(v)
})
//输出结果 a b

p1 不是用立即值而是用另一个promise p3决议,后者本身决议为值'b',规定的行为是把p3展开到p1,但是是异步地展开。所以在异步任务队列中,p1的回调排在p2的回调之后