JS异步编程:Promise深入使用、async/await使用

450 阅读7分钟

这篇文章基于前两篇文章:

JS异步编程:Promise使用方法梳理

JS异步编程:事件队列和事件循环机制

Promise深入使用

then 返回结果总结

执行 then 方法会返回一些全新的 promise 实例p2

p2的状态和值是如何改变的?

不论执行的是基于 p1.then 存放的 onfulfilledCallback/onrejectedCallback 两个方法中的哪一个 :

  • 如果方法执行不报错
    • 如果方法中返回一个全新的 Promise 实例,则“全新的 Promise实例”的成功和失败决定p2的成功和失败
    • 如果不是返回 promise ,则 [[PromiseState]]:fulfiled [[PromiseResult]] :两个方法中任何一个的返回值
  • 如果方法执行报错:p2的 [[PromiseState]]:rejected , [[PromiseResult]]:报错原因
let p1 = new Promise((resolve, reject) => {
    resolve('OK');
    //reject('NO');
});
let p2 = p1.then(result => {
    console.log('P1成功-->', result);
    return Promise.reject(10);
}, reason => {
    console.log('P1失败-->', reason);
});
let p3 = p2.then(result => {
    console.log('P2成功-->', result);
}, reason => {
    console.log('P2失败-->', reason);
    return Promise.resolve(10);
});

p3.then(result => {
    console.log('P3成功-->', result);
}, reason => {
    console.log('P3失败-->', reason);
    return Promise.resolve(10);
});
console.log(1);

image。png

resolve('OK'); 注释结果:

image。png

promise链顺延/穿透总结

如果 onfulfilledCallback / onrejectedCallback 不传递,则状态和结果都会“顺延/穿透”到下一个同等状态应该执行的回调函数上(内部其实是自己补充了一些实现效果的默认函数)

new Promise((resolve, reject) => {
        resolve('OK');
    }).then(null, null)
    .then(result => {
        console.log('成功-->', result);
    }, reason => {
        console.log('失败-->', reason);
    })

相当于(内部会做处理):

new Promise((resolve, reject) => {
        resolve('OK');
    }).then(result=>result ,reason=>Promise.reject(reason) )
    .then(result => {
        console.log('成功-->', result);
    }, reason => {
        console.log('失败-->', reason);
    })
new Promise((resolve, reject) => {
        resolve('OK');
       // reject('NO');
    }).then(null /*result=>result*/ , null /* reason=>Promise.reject(reason) */ )
    .then(result => {
        console.log('成功-->', result);
    }, reason => {
        console.log('失败-->', reason);
    }).then(result => {
        console.log('成功-->', result);
    }, reason => {
        console.log('失败-->', reason);
    }); 

resolve 打印:

image。png

reject 打印:

image。png

catch 只是 then 的语法糖,处理状态为失败下做的事情

Promise.prototype.catch = function (onrejectedCallback) {
    return this.then(null, onrejectedCallback);
};
new Promise((resolve, reject) => {
    resolve('OK');
    // reject('NO');
}).then(result => {
    console.log('成功-->', result);
}).then(result => {
    console.log('成功-->', result);
    return Promise.reject('xx');
}).catch(reason => {
    console.log('失败-->', reason);
});

resolve 打印:

image。png

reject 打印:

image。png

跳过其他处理,直接到最后的 catch

new Promise((resolve, reject) => {
    resolve('OK');
    // reject('NO');
}).then(result => {
    console.log('成功-->', result);
}).then(result => {
    console.log('成功-->', result);
    return Promise.reject('xx');
}).catch(reason => {
    console.log('失败-->', reason);
});

image。png 不会跳过

Promise.all / Promise.race

Promise.all 方法上一篇文章写了使用方法,这边举几个例子来理解使用

Promise.all([promise数组]) promise数组要求数组中的每一项尽可能都是promise实例。返回一个新的promise实例A,A成功还是失败,取决于数组中的每一个promise实例是成功还是失败,只要有一个是失败,A就是失败的,只有都成功A才是成功的

Promise.race :最先知道状态的promise实例,是成功还是失败,决定了A是成功还是失败

 function fn(interval) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(interval);
        }, interval);
    });
}
let p1 = fn(3000);
let p2 = fn(1000);
let p3 = Promise.resolve(0);

Promise.all([p1, p2, p3]).then(results => {
    // 不论谁先知道状态,最后结果的顺序和传递数组的顺序要保持一致
    console.log(results);
}); 

3s后:

image。png

function fn(interval) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(interval);
        }, interval);
    });
}
let p1 = fn(3000);
let p2 = fn(1000);
let p3 = Promise.reject(0);

Promise.all([p1, p2, p3]).then(results => {
    // 不论谁先知道状态,最后结果的顺序和传递数组的顺序要保持一致
    console.log(results);
}).catch(reason => {
    // 处理过程中,遇到一个失败,则All立即为失败,结果就是当前实例失败的原因
    console.log(reason);
});

直接立刻打印 0

需求处理:

const api1 = () => {
    return new Promise(resolve => {
        $.ajax({
            url: '/api1',
            success(result1) {
                resolve(result1);
            }
        });
    });
};
const api2 = () => {
    return new Promise(resolve => {
        $.ajax({
            url: '/api2',
            success(result2) {
                resolve(result2);
            }
        });
    });
};
const api3 = () => {
    return new Promise(resolve => {
        $.ajax({
            url: '/api3',
            success(result3) {
                resolve(result3);
            }
        });
    });
};

api1 / api2 / api3串行请求:

api1().then(result1 => {
    return api2();
}).then(result2 => {
    return api3();
}).then(result3 => {

});

更好的串行请求:

(async function () {
    let result1 = await api1();
    let result2 = await api2();
    let result3 = await api3();
})();

并行请求:

Promise.all([api1(), api2(), api3()]).then(results => {
    
});

async / await

async / await :ES7提出, generator + promise 的语法糖

async函数-MDN

await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当作该 await 表达式的返回值。使用 async / await 关键字就可以在异步代码中使用普通的 try / catch 代码块

image。png

image。png 需要注意的点:

  1. async:函数修饰符,控制函数返回promise实例

    async function fn() {
        return 10;
    }
    console.log(fn());
    

    image。png

  2. 函数内部执行报错,则返回失败的promise实例,值是失败的原因

    async function fn() {
        console.log(a)
    }
    console.log(fn());
    

    image。png

  3. 自己返回一个promise,以自己返回的为主

    async function fn() {
        return Promise.resolve(1);
    }
    console.log(fn());
    
    

    image。png

  4. 如果函数内部做了异常捕获,则还是成功态

    async function fn() {
        try{
            console.log(a)
        }catch(e){}
        return 10
    }
    console.log(fn());
    

    image。png

使用 async 的主要目的:是为了在函数内部使用 await

function fn() {
    await 1; //Uncaught SyntaxError: await is only valid in async function
} 
  1. await后面应该放置一个promise实例。我们书写的代码不是,浏览器也会把其变为promise实例 await 1 -> await Promise.resolve(1)
  2. await中断函数体中,await表达式会暂停整个async函数的执行进程并出让其控制权。只有等待await后面的promise实例是成功态之后,才会把之前暂停的代码继续执行,如果后面的promise实例是失败的,则下面的代码就不再执行了。
  3. await是异步的微任务。函数体中遇到await,下面的代码会暂停执行,会把他们当做一个任务,放置在EventQueue的微任务队列中
function api(interval) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(interval);
        }, interval);
    });
}

async function func() {
    // await 1; //-> await Promise.resolve(1);

    let result1 = await api(1000);
    console.log(result1);

    let result2 = await api(3000);
    console.log(result2);
}
func();

async/await 总结

  1. Promise.then链中如果嵌套过多的话,仍然可能出现回调地狱的问题

  2. async+await是es7提出来的概念,它也是为了解决回调地狱的问题,它只是一种语法糖。

  3. async/await把异步操作变得更像同步操作

async

  1. async用于声明一个异步函数,该函数执行完之后总会返回一个 Promise 对象,如果不是Promise,会使用Promise.resolve()将结果包装成Promise对象

  2. 执行完这个方法后返回值可以用then来操作

await

  1. await 操作符用于等待一个 Promise 对象,它只能在异步函数 async function 内部使用。
  2. await操作符标示的内容会同步执行,等待后面的Promise对象的异步操作有了结果才执行后面的代码。await语句的返回值就是异步的结果值

await的串并行

有下面两个函数:

var resolveAfter2Seconds = function resolveAfter2Seconds() {
    console.log("starting slow promise");
    return new Promise(resolve => {
        setTimeout(function () {
            resolve("slow");
            console.log("slow promise is done");
        }, 2000);
    });
};

var resolveAfter1Second = function resolveAfter1Second() {
    console.log("starting fast promise");
    return new Promise(resolve => {
        setTimeout(function () {
            resolve("fast");
            console.log("fast promise is done");
        }, 1000);
    });
};

不同的运行方法有不同的结果:

相继(串行)

// sequential:相继的 /[sɪˈkwenʃl]/
var sequential = async function sequential() {
    console.log('==SEQUENTIAL START==');
    const slow = await resolveAfter2Seconds();
    console.log(slow);
    const fast = await resolveAfter1Second();
    console.log(fast);
};
sequential()

串行 image。png

同时(并行)

// concurrent:同时发生的 /[kənˈkʌrənt]/
var concurrent = async function concurrent() {
    console.log('==CONCURRENT START with await==');
    const slow = resolveAfter2Seconds();
    const fast = resolveAfter1Second();
    console.log(await slow);
    console.log(await fast);
};
concurrent()

前面两行会直接同步运行,什么时候遇到await,就会停止下来

await 中断函数体中, await 表达式会暂停整个 async 函数的执行进程并出让其控制权。只有等待 await 后面的promise实例是成功态之后,才会把之前暂停的代码继续执行,如果后面的promise实例是失败的,则下面的代码就不再执行了。

await slow 相当于 await resolveAfter2Seconds()

所以结果为:

image。png

console.log(await slow);
console.log(await fast)

什么时候等slow结束,什么时候打印两个

如果是这样:

// concurrent:同时发生的 /[kənˈkʌrənt]/
var concurrent = async function concurrent() {
    console.log('==CONCURRENT START with await==');
    const slow = resolveAfter2Seconds();
    const fast = resolveAfter1Second();
    console.log(await fast);
    console.log(await slow);
};
concurrent()

image。png

Promise.all 的同步方式:

var concurrentPromise = function concurrentPromise() {
    console.log('==CONCURRENT START with Promise.all==');
    return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()])
        .then((messages) => {
            console.log(messages[0]);
            console.log(messages[1]);
        });
};

image。png

平行(也相当于并行)

// parallel:平行的 /[ˈpærəlel]/
var parallel = async function parallel() {
    console.log('==PARALLEL with await Promise.all==');
    await Promise.all([
        (async () => {
            let result = await resolveAfter2Seconds();
            console.log(result);
        })(),
        (async () => {
            let result = await resolveAfter1Second();
            console.log(result);
        })(),
    ]);
};

两个字执行函数有自己内部的私有上下文,互不影响。在这两个自执行函数所在的上下文,都是同步运行两个函数的。所以相当于这样:

var parallel2 = async function parallel() {
    console.log('==PARALLEL with await Promise.all==');
    var slow =  (async () => {
        let result = await resolveAfter2Seconds();
        console.log(result);
    })()
    var fast =  (async () => {
        let result = await resolveAfter1Second();
        console.log(result);
    })()
    await Promise.all([slow,fast]);
};

image。png

var parallelPromise = function parallelPromise() {
    console.log('==PARALLEL with Promise.then==');
    resolveAfter2Seconds().then((message) => console.log(message));
    resolveAfter1Second().then((message) => console.log(message));
};

输出一样: image。png