Promise详解

183 阅读9分钟

promise

MDN文档

1. 引入promise

// 向服务器发送请求
// /student?class=1  获取班级学员信息 
// /score?stuId=[1,2,3] 返回学员的分数信息
// /jige?score=90  确定是否及格
    $.ajax({
            url: '/student',
            method: 'get',
            data: {
                    class: 1
            },
            success: function (result) {
                    // result=>学生信息
            $.ajax({
                url: '/score',
                method: 'get',
                data: {
                        stuId: result.map(item => item.id)
                },
                success: function (result) {
                    // result=>学员的分数信息
                    $.ajax({
                            //...
                    });
                }
            });
			}
		});
  1. 回调地狱: 上一个回调函数中继续做事情,而且继续回调(在真实项目的AJAX请求中经常出现回调地狱)
    =>异步请求、不方便代码的维护

  2. Promise的诞生就是为了解决异步请求中的回调地狱问题:它是一种设计模式,ES6中提供了一个JS内置类Promise,来实现这种设计模式。

function ajax1() {
    return new Promise(resolve => {
        $.ajax({
            url: '/student',
            method: 'get',
            data: {
                    class: 1
            },
            success: resolve
        });
    });
        }

function ajax2(arr) {
    return new Promise(resolve => {
        $.ajax({
            url: '/score',
            method: 'get',
            data: {
                    stuId: arr
            },
            success: resolve
        });
    });
}

function ajax3() {
    return new Promise(resolve => {
        $.ajax({
            url: '/jige',
            // ...
            success: resolve
        });
    });
}
ajax1().then(result => {
                return ajax2(result.map(item => item.id));
        }).then(result => {
                return ajax3();
        }).then(result => {

        });
  1. 改用async的方式
async function handle() {
    let result = await ajax1();
    result = await ajax2(result.map(item => item.id));
    result = await ajax3();
    // 此处的result就是三次异步请求后获取的信息
}
handle();

2. promise概念

1. executor

PROMISE是用来管理异步编程的,它本身不是异步的:new Promise的时候会立即把executor函数执行(只不过我们一般会在executor函数中处理一个异步操作)

new Promise([executor]) // [executor]执行函数是必须传递的
let p1 = new Promise(() => {
            console.log(1); //=>1
        });
        console.log(2); //=>2 
// 	先1 后2.立即执行1
		
let p1 = new Promise(() => {
            setTimeout(_ => {
                    console.log(1);
            }, 1000);
            console.log(2);
        });
console.log(3);

// 先执行promise函数,输出2,然后3,1s后定时器输出1
let p1 = new Promise(() => {
            setTimeout(_ => {
                    console.log(1);
            }, 1000);
            console.log(2);
    });
console.log(3); 
// 2 3 1

2. promise的三个状态和value值

  1. 三个状态

pending 初始状态

fulfilled 代表操作成功(resolved)

rejected 代表当前操作失败

  1. value值

PROMISE本身有一个VALUE值,用来记录成功的结果(或者是失败的原因的) =>[[PromiseValue]]

let p1 = new Promise((resolve, reject) => {
        setTimeout(_ => {
                // 一般会在异步操作结束后,执行resolve/reject函数,执行这两个函数中的一个,都可以修改Promise的[[PromiseStatus]]/[[PromiseValue]]
                // 一旦状态被改变,在执行resolve、reject就没有用了
                resolve('ok');
                reject('no');
        }, 1000);
      }); 

3. promise的then方法

  1. new Promise的时候先执行executor函数,在这里开启了一个异步操作的任务(此时不等:把其放入到EventQuque任务队列中),继续执行。
  2. p1.then基于THEN方法,存储起来两个函数(此时这两个函数还没有执行);当executor函数中的异步操作结束了,基于resolve/reject控制Promise状态,从而决定执行then存储的函数中的某一个。
let p1 = new Promise((resolve, reject) => {
    setTimeout(_ => {
        let ran = Math.random();
        console.log(ran);
        if (ran < 0.5) {
                reject('NO!');
                return;
        }
        resolve('OK!');
    }, 1000);
});
    // THEN:设置成功或者失败后处理的方法
    // Promise.prototype.then([resolvedFn],[rejectedFn])
p1.then(result => {
        console.log(`成功:` + result);
}, reason => {
        console.log(`失败:` + reason);
}); 

练习: 1.

let p1 = new Promise((resolve, reject) => {
            console.log(1)
            resolve(100);
            console.log(2)
        });
p1.then(result => {
    console.log(`成功:` + result);
}, reason => {
    console.log(`失败:` + reason);
});
console.log(3);
// 1 2 3 成功:100
let p1 = new Promise((resolve, reject) => {
    // resolve/reject 的执行,不论是否放到一个异步操作中,
    // 都需要等待then先执行完,把方法存储好
    //,才会在更改状态后执行then中对应的方法.
    // =>此处是一个异步操作(所以很多人说PROMISE是异步的),而且是微任务操作
            resolve(100);
    });
p1.then(result => {
        console.log(`成功:` + result);
}, reason => {
        console.log(`失败:` + reason);
});
console.log(3);


    // 3 再输出 100
  1. 创建一个状态为成功/失败的PROMISE实例
    let p1 = new Promise((resolve, reject) => {
                    resolve(100);
             }) 
//创建一个状态为成功PROMISE实例 , 这样的写法也可以被这种写法代替 
    //=> Promise.resolve(100)
    // => Promise.reject(0)
	

4. THEN链

let p1 = new Promise((resolve, reject) => {
            resolve(100);
    }) //创建一个状态为成功PROMISE实例 ;

let p2 = p1.then(result => {
        console.log('成功:' + result);
        return result + 100; 
}, reason => {
        console.log('失败:' + reason);
        return reason - 100;
});

let p3 = p2.then(result => {
        console.log('成功:' + result);
}, reason => {
        console.log('失败:' + reason);
}); 
    //成功200 成功100

  1. THEN方法结束都会返回一个新的Promise实例(THEN链)
[[PromiseStatus]]:'pending'
[[PromiseValue]]:undefined
  1. p1这个new Promise出来的实例,成功或者失败,取决于executor函数执行的时候,执行的是resolve还是reject决定的,再或者executor函数执行发生异常错误,也是会把实例状态改为失败的
  2. p2/p3这种每一次执行then返回的新实例的状态,由then中存储的方法执行的结果来决定最后的状态(上一个THEN中某个方法执行的结果,决定下一个then中哪一个方法会被执行)
  • 不论是成功的方法执行,还是失败的方法执行(THEN中的两个方法),凡是执行抛出了异常,则都会把实例的状态改为失败
  • 方法中如果返回一个新的PROMISE实例,返回这个实例的结果是成功还是失败,也决定了当前实例是成功还是失败
  • 剩下的情况基本上都是让实例变为成功的状态 (方法返回的结果是当前实例的value值:上一个then中方法返回的结果会传递到下一个then的方法中)
  1. 练习
Promise.resolve(1)
     .then(result => {
            console.log(`成功:${result}`); // 成功1 
            return result * 10; 
    }, reason => {
            console.log(`失败:${reason}`);
    }).then(result => {
            console.log(`成功:${result}`); // 成功10
    }, reason => {
            console.log(`失败:${reason}`);
    });
new Promise((resolve)=>{
           resolve(a) // => 报错,状态变为失败
    })
    .then(result => {
            console.log(`成功:${result}`); 
            return result * 10; 
    }, reason => {
            console.log(`失败:${reason}`); //=> 失败 a is not defined
    })
    //上面执行的是是成功的,所以状态是成功的。只不过没有返回结果
    .then(result => {
            console.log(`成功:${result}`); //=》 成功 undefined 
    }, reason => {
            console.log(`失败:${reason}`); 
    }); 
Promise.resolve(10)
      .then(result => {
            console.log(`成功:${result}`); 
            return Promise.reject(result * 10);
    }, reason => {
            console.log(`失败:${reason}`);
    }).then(result => {
            console.log(`成功:${result}`);
    }, reason => {
            console.log(`失败:${reason}`); 
    }); 
    // 成功 10  失败 100
  1. TEHN中也可以只写一个或者不写函数

.then(fn)

.then(null,fn)

遇到一个THEN,要执行成功或者失败的方法,如果此方法并没有在当前THEN中被定义,则顺延到下一个对应的函数

 Promise.reject(10).then(result => {
			console.log(`成功:${result}`);
			return result * 10;
		}).then(null, reason => {
			console.log(`失败:${reason}`); // 失败 10
		}); 

5. catch

Promise.prototype.catch(fn) ===> .then(null,fn)

Promise.resolve(10).then(result => {
			console(a);//=>报错了,让状态变为失败
		}).catch(reason => {
			console.log(`失败:${reason}`); // 失败 a is not defined
		}); 

6. Promise.all

MDN文档

Promise.all([promise1, promise2]).then(success1, fail1)
  1. 返回的结果是一个PROMISE实例(ALL实例),要求ARR数组中的每一项都是一个新的PROMIE实例。
  2. PROMISE.ALL是等待所有数组中的实例状态都为成功才会让“ALL实例”状态为成功,VALUE是一个集合,存储着ARR中每一个实例返回的结果。
  3. 凡是ARR中有一个实例状态为失败,“ALL实例”的状态也是失败。
let p1 = Promise.resolve(1);
let p2 = new Promise(resolve => {
        setTimeout(_ => {
                resolve(2);
        }, 1000);
});
let p3 = Promise.reject(3);

Promise.all([p2, p1,p3]).then(result => { // 有p3就失败,没有p3成功。
        // 返回的结果是按照ARR中编写实例的顺序组合在一起的,不是谁先到先执行谁
        // [2,1]
        console.log(`成功:${result}`); //成功 [2 1]
}).catch(reason => {
        console.log(`失败:${reason}`);
}); 
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

7. Promise.race

MDN文档

 Promise.race([promise1, promise2]).then(success1, fail1)
  • promise1和promise2只要有一个成功就会调用success1;
  • promise1和promise2只要有一个失败就会调用fail1;
  • 总之,谁第一个成功或失败,就认为是race的成功或失败。
  1. 和ALL不同的地方,RACE是赛跑,也就是ARR中不管哪一个先处理完,处理完的结果作为“RACE实例”的结果。
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

8. ES7中提供了PROMISE操作的语法糖:async / await

  1. async
  • async 是让一个普通函数返回的结果变为STATUS=RESOLVED并且VALUE=RETRN结构的PROMISE实例
  • async 最主要的作用是配合await使用的,因为一旦在函数中使用await,那么当前的函数必须用async修饰
async function fn() {
        return 10;
}
console.log(fn());

ctLwRA.png

  1. await会等待当前promise的返回结果,只有返回的状态是RESOLVED情况,才会把返回结果赋值给RESULT。
  2. await不是同步编程,是异步编程(微任务):当代码执行到此行(先把此行),构建一个异步的微任务(等待PROMISE返回结果,并且AWAIT下面的代码也都被列到任务队列中)。 一段代码:
let p1 = Promise.resolve(100);
async function fn() {
    console.log(1);
    let result = await p1;
    console.log(result);
}
fn();
console.log(2); 
	

输出: 1 2 100

let p1 = Promise.resolve(100);
let p2 = new Promise(resolve => {
        setTimeout(_ => {
                resolve(200);
        }, 1000);
});
let p3 = Promise.reject(3);

async function fn() {
        console.log(1);
        // 有2 个await
        let result = await p2;
        console.log(result); // 200
        let AA = await p1;
        console.log(AA); // 100 
}
fn();
console.log(2);

输出: 1 2 200 100
4. 如果promise是失败状态,则await不会接收其返回结果,await下面的代码也不会在继续执行(await只能处理PROMISE为成功状态的时候)

let p3 = Promise.reject(3);
async function fn() {
        let reason = await p3;
        console.log(reason);
}
fn();

9. 题

1.

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
promise2
undefined
setTimeout
  • 解析:
  1. 主线程自上而下执行,创建async1函数,async2函数。
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');

console.log("script start") => script start 2. 遇到setTimout,设置定时器1。属于宏任务,0ms => 执行任务(设置定时器是同步任务,而多少秒后执行,属于异步宏任务)

setTimeout(function () {
    console.log('setTimeout');
}, 0)
  1. 执行async1(), console.log(" async1 start ") =>async1 start
  2. 遇到
await async2();

立即执行async2()(即await后面的代码),但这行下面的代码需要等返回正确的promise结果再执行,也就是说这行下面的一行或几行属于异步任务(微任务),我们将他命名为微任务2。=> async2

  1. 继续执行主线程里的代码,遇到
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});

new Promise是同步任务,立即执行executor。所以输出promise1。 resolve()是异步操作(微任务),用.then()存储响应的方法到异步队列中,将他命名为微任务3。=> promise1

  1. 继续执行主线程中的任务
console.log('script end'); 

输出script end => script end

  1. 此时主线程中的同步任务都执行完了,开始执行事件队列中的异步任务。先微任务,再宏任务
    • 此时任务队列中微任务有微任务2,3.宏任务有定时器1。
  2. 执行微任务2:=> async1 end
  3. 执行微任务3: => promise 2
  4. 执行宏任务(定时器1):=> setTimeout

2.题

console.log(1);
setTimeout(_ => { console.log(2); }, 1000);
async function fn() {
        console.log(3);
        setTimeout(_ => {  console.log(4); }, 20);
        return Promise.reject();
}
async function run() {
        console.log(5);
        await fn();
        console.log(6);
}
run();
// 需要执行150MS左右
for (let i = 0; i < 90000000; i++) {}
setTimeout(_ => {
        console.log(7);
        new Promise(resolve => {
                console.log(8);
                resolve();
        }).then(_ => { console.log(9); });
}, 0);
console.log(10);

答案:

1
5
3
10
4
7
8
9
2

解析:

  1. console.log(1) => 1
  2. 设置定时器1,1000ms后,输出 =>2.
    setTimeout(_ => { console.log(2); }, 1000);
  1. 创建fn()函数,只创建
async function fn() {
    console.log(3);
    setTimeout(_ => {  console.log(4); }, 20);
    return Promise.reject();
}
  1. 创建run()函数
async function run() {
        console.log(5);
        await fn();
        console.log(6);
}
  1. 执行run()
async function run() {
        console.log(5);
        await fn();
        console.log(6);
}
  • 先输出5 => 5
  • await fn()立即执行fn(),而它下面的异步代码,需要等fn返回成功态才会执行。
  • 设置下面的代码为微任务2(即fn执行的结果是成功后,输出6)。
  1. 执行fn()
async function fn() {
    console.log(3);
    setTimeout(_ => {  console.log(4); }, 20);
    return Promise.reject();
}
  • 先输出3 => 3
  • 设置定时器。命名为定时器3。20ms输出4
  • 返回一个失败状态
  • 由于失败了,所以微任务2不会执行
  1. 遇到循环
for (let i = 0; i < 90000000; i++) {}

需要执行150MS左右,也就是说要等待150ms 8. 设置定时器4

setTimeout(_ => {
        console.log(7);
        new Promise(resolve => {
                console.log(8);
                resolve();
        }).then(_ => { console.log(9); });
}, 0);
  1. 输出10 => 10
  2. 此时主线程任务完成,开始执行任务队列。先微任务,后宏任务。微任务中没有需要执行的。所以执行宏任务。
  3. 此时定时器3已经完成计时,可以执行=> 4
  4. 定时器4执行,先输出7 => 7
  5. 定时器4中new promise
  • executor先执行,输出8 => 8
  • resolve()是一个微任务,放入队列。命名为微任务5(会把后期then方法中存储的第一个方法执行)
  1. 队列中出现了微任务,不管此时宏任务时候已经完成,都要先执行微任务。输出微任务5 => 9
  2. 执行宏任务1 => 2

10. 手写promise